Svelte Shy Header: Peekaboo Sticky Header with CSS
Tags: webdev, css, javascript, svelte
Published: 2022-08-10 10:07:16

---
title: "Svelte Shy Header: Peekaboo Sticky Header with CSS"
published: "true"
description: "Svelte shy header: how to add a vanishing, sticky peekaboo header that reappears when the user scrolls up using Svelte and CSS to your site."
tags: "webdev, css, javascript, svelte"
canonical_url: "https://rodneylab.com/svelte-shy-header/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5as07t18cylxtkxoun1j.png"
---

## ☺️ Svelte Shy Header: What on Earth is a Shy Header?

I wanted a Svelte shy header solution to add to a site I was working on and came across a Tweet on a <a aria-label="See the Tweet about the shy header" href="https://twitter.com/jaffathecake/status/1547884638227509248">solution by Jake Archibald</a> and <a aria-label="Open Robert Flacks Twitter profile" href="https://twitter.com/flackrw">Robert Flack</a> from Google&rsquo;s Chromium team.  I needed the solution for a demo site; adding some text in the header to describe the setup a little.  As an extra feature to improve **user experience (UX)**, I wanted the header to **re-appear if the user scrolled upwards** again.  That is even if they were already halfway down the page.  The idea being to save them having to scroll all the way back up to the top to see site setup blurb.

I saw no need to use a library as the implementation was fantastic. Jake and Robert&rsquo;s solution leaned heavily on **modern CSS**, with a  I wondered how much I could **Sveltify** it though.  For example, make use of Svelte **style directives**, element **dimension bindings** and what not. Basically a kitchen sink, all-out Svelte approach.

If you are new to Svelte you might find this post useful as a showcase of some of Svelte&rsquo;s templating and styling features.  If you have more experience, you probably have some ideas on how to make it even Sveltier! Reach out in a comment below if you do.

## Original HTML / CSS / JavaScript Solution

<a aria-label="Open Jake’s solution" href="https://codepen.io/jaffathecake/pen/OJvbpRZ">Jake&rsquo;s full solution is on CodePen</a> and uses plain HTML, CSS and JavaScript.  Here is a summary:

```html
<div class="header-shifter"></div>
<div class="header">Header</div>
<p>First paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
<p>Paragraph</p>
```

```css
body {
  margin: 0;
}

.header {
  position: relative;
  background: blue;
  color: white;
  padding: 20px;
}
```

```javascript
// This is adaptation of https://jsbin.com/mamisar/edit?html,css,js,output
// By https://twitter.com/flackrw
 
const header = document.querySelector('.header');
const headerShifter = document.querySelector('.header-shifter');
 
header.style.position = 'sticky';
header.style.top = 'calc(var(--computed-height) * -1 - 1px)';
header.style.bottom = 'calc(100% - var(--computed-height))';
 
function fixHeaderoffset() {
  const { height } = header.getBoundingClientRect();
  header.style.setProperty('--computed-height', height + 'px');
 
  const y = Math.min(
    header.offsetTop, 
    document.documentElement.scrollHeight - innerHeight - height
  );
  headerShifter.style.height = y + 'px';
  header.style.marginBottom = -y + 'px';
}
 
addEventListener('scroll', () => fixHeaderoffset());
addEventListener('resize', () => fixHeaderoffset());
fixHeaderoffset();
```

## 🔫 Svelte Shy Header: Sveltification Plan of Action

Here&rsquo;s my initial thinking on the Sveltification process:

- you can easily bind a JavaScript variable to an element&rsquo;s height with Svelte&rsquo;s **dimension bindings**,
- Svelte has some nice **syntactic sugar** for adding **event listeners**.  We will try to work these in for the scroll and resize events,
- there&rsquo;s also a **Svelte way to bind an element** to a JavaScript variable. This will simplify any element style updates we keep in the JavaScript code.  We will try to move some of them down to the element using style directives.

Enough talk, time for some action.

## 🧱 Svelte Shy Header: Code Along

If you are new to Svelte, hopefully this little project can provide a gentle introduction.  Here&rsquo;s how you can spin up a new project.  Start by running these commands in the Terminal:

```shell
pnpm create svelte@next svelte-shy-header
cd svelte-shy-header
pnpm install
pnpm install @fontsource/playfair-display # installs a font used later for self-hosting
pnpm dev
```

You will get some options, choose a *Skeleton project* and whatever you like for the others.  The last command will start up a dev server and spit out a URL you can paste into you browser.  We will only create or change two files so just paste the code in when we get to that point.  Also make sure you play with, breaking and repairing stuff, to understand better what it is doing!

## 🏠 Home Page

I thought why not throw in a background CSS pattern?  I opted for customising the <a aria-label="See the original S V G pattern" href="https://www.svgbackgrounds.com/set/free-svg-backgrounds-and-patterns/">Colorful Stingrays pattern from www.svgbackgrounds.com</a>.  Here is the full code for the home page (replace existing content in `src/routes/index.svelte` if you are coding along):

```svelte
<script>
	import Header from '$lib/components/Header.svelte';
	import '@fontsource/playfair-display/latin.css';
</script>
 
<Header />
<main class="container" />
 
<style>
	:global(body) {
		margin: 0;
		font-family: Playfair Display;
	}
	:global(:after, :before) {
		box-sizing: border-box;
	}
	:global(:root) {
		--font-size-6: 3.052rem;

		--font-weight-bold: 700;

		--max-width-full: 100%;
		--spacing-6: 1.5rem;

		--colour-brand: hsl(19 97% 51%);
		--colour-dark: hsl(345 6% 13%);
	}
	.container {
		height: 500vh;
		width: var(--max-width-full);

		/* CREDIT: https://www.svgbackgrounds.com/ */
		background-color: #3d315b;
		background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='100' viewBox='0 0 600 100'%3E%3Cg stroke='%23FFF' stroke-width='0' stroke-miterlimit='10' %3E%3Ccircle fill='%23FB5607' cx='0' cy='0' r='50'/%3E%3Ccircle fill='%23FC7839' cx='100' cy='0' r='50'/%3E%3Ccircle fill='%23F92A82' cx='200' cy='0' r='50'/%3E%3Ccircle fill='%23F9EA92' cx='300' cy='0' r='50'/%3E%3Ccircle fill='%23F4DA52' cx='400' cy='0' r='50'/%3E%3Ccircle fill='%23EFCA08' cx='500' cy='0' r='50'/%3E%3Ccircle fill='%23FB5607' cx='600' cy='0' r='50'/%3E%3Ccircle cx='-50' cy='50' r='50'/%3E%3Ccircle fill='%23fc6824' cx='50' cy='50' r='50'/%3E%3Ccircle fill='%23ff505a' cx='150' cy='50' r='50'/%3E%3Ccircle fill='%233D315B' cx='250' cy='50' r='50'/%3E%3Ccircle fill='%23f6e273' cx='350' cy='50' r='50'/%3E%3Ccircle fill='%23f1d237' cx='450' cy='50' r='50'/%3E%3Ccircle fill='%23fa9500' cx='550' cy='50' r='50'/%3E%3Ccircle cx='650' cy='50' r='50'/%3E%3Ccircle fill='%23FB5607' cx='0' cy='100' r='50'/%3E%3Ccircle fill='%23FC7839' cx='100' cy='100' r='50'/%3E%3Ccircle fill='%23F92A82' cx='200' cy='100' r='50'/%3E%3Ccircle fill='%23F9EA92' cx='300' cy='100' r='50'/%3E%3Ccircle fill='%23F4DA52' cx='400' cy='100' r='50'/%3E%3Ccircle fill='%23EFCA08' cx='500' cy='100' r='50'/%3E%3Ccircle fill='%23FB5607' cx='600' cy='100' r='50'/%3E%3Ccircle cx='50' cy='150' r='50'/%3E%3Ccircle cx='150' cy='150' r='50'/%3E%3Ccircle cx='250' cy='150' r='50'/%3E%3Ccircle cx='350' cy='150' r='50'/%3E%3Ccircle cx='450' cy='150' r='50'/%3E%3Ccircle cx='550' cy='150' r='50'/%3E%3C/g%3E%3C/svg%3E");
	}
</style>
```

This is a typical Svelte file with three sections: `script` tag where we run the JavaScript Code, the Svelte markup, then the `style` tag.  We place the Header in its own component file (imported in line `2`).  This makes it easier to recycle it for use on other pages.  On top you can <a aria-label="Open video on creating a Svelte component library" href="https://rodneylab.com/create-sveltekit-component-library/">easily create a Svelte component library</a>, letting you share the code between your other Svelte projects.

## 🙈 Svelte Shy Header Component

If coding along, create a `src/lib/components/Header.svelte` file (you will need to create a couple of new folders).  That is the file we look at now.  This file will eventually have the three sections we have in the home page, but we will start with the middle section:

```svelte
<svelte:window on:scroll={fixHeaderOffset} on:resize={fixHeaderOffset} />

<div style:height="{headerShifterHeight}px" class="header-shifter" />
<div
	bind:this={header}
	bind:clientHeight={headerHeight}
	style:margin-bottom="-{headerShifterHeight}px"
	class="header"
>
	<header class="wrapper">☺️ Svelte Shy Header 🙈</header>
</div>
```

In line `1`, we add the event listeners for `scroll` and `resize` events, but the Svelte way!  `fixHeaderOffset` is the function we will invoke on these events.  We will define it later.

Next in like `3`, we have the `header-shifter` div.  We keep the original class name but also add:

```svelte
style:height="{headerShifterHeight}px"
```

As you might guess, we are setting the element&rsquo;s height here, using CSS.  This is a Svelte **style directive**.  In place of `height` you can have any regular CSS property of even a custom CSS property (often called CSS variable).  `headerShifterHeight` is a number variable which we will define in the `script` tag.  The `px` is just to add expected units for valid CSS.  An alternative would have been to write something like this, making use of the element&rsquo;s style attribute and JavaScript template strings:


```html
<!-- LESS OPTIMAL -->
<div style={`height=${headerShifterHeight}px`} class="header-shifter" />
```

It is Svelte best practice to avoid this, just because, using the original code, the Svelte compiler optimises the output.  It will only update styles on the element if `headerShifterHeight` changes.


### `header` Element

Let&rsquo;s see the header div now (lines `4`&ndash;`9` above).  Here it is again:

```svelte
<div
 	bind:this={header}
 	bind:clientHeight={headerHeight}
 	style:margin-bottom="-{headerShifterHeight}px"
 	class="header"
 >
```

We just saw a style directive and the class attribute is nothing new.  That leaves the two `bind` statements.  The first `bind:this={header}` is another Sveltism.  This lets us bind the element itself to the JavaScript variable `header`.  That saves us writing out a query selector when we get to writing the script tag.  It&rsquo;s just syntactic sugar making the code a little cleaner.

The second `bind` directive it where we link the header&rsquo;s height to the `headerHeight` JavaScript variable.  This will save us calling `getBoundingClientRect` later in the `script` tag. 

## 💄 Svelte Style

Paste this code in if you are coding along

```svelte
<style>
	.header-shifter {
		background-color: var(--colour-dark);
	}
	.header {
		--_computed-height: var(--computed-height, 165px);
		position: relative;
		background: var(--colour-dark);
		color: var(--colour-brand);
		padding: var(--spacing-6);
	}
	.wrapper {
		width: var(--max-width-full);
		margin: var(--spacing-6) auto;
		max-width: var(--max-width-full);
		font-size: var(--font-size-6);
		font-weight: var(--font-weight-bold);
		text-align: center;
	}
</style>
```

We are adding a touch more style vs. the original version, but not really changing anything fundamentally.  The main change is the `--_computed-height` custom CSS property in line 18.  The CSS custom property spec lets us specify a fallback value as a second argument.  CSS will use this, second, value if `--computed-height` is not set.  You will notice we do not set it anywhere here in the CSS.  As in the original solution we set it using JavaScript.  However, if the user has JavaScript disabled in their browser, we can rely on the fallback value we provide here.  This is not strictly necessary (the property is only used in styles added by JavaScript), but provides nice demo of how you can define defaults.  Props to <a aria-label="Open article on style prop defaults" href="https://geoffrich.net/posts/style-prop-defaults/">Geoff Rich for this technique</a>.

## 🐇 Svelte Shy Header: Java Script

Finally, we can see the much trailed `script` tag!  Once again, paste this in (right at the top of `src/lib/components/Header.svelte`) if you are coding along.

```svelte
<script lang="ts">
	/* This is code is based on an adaptation by https://twitter.com/jaffathecake of
	https://jsbin.com/mamisar/edit?html,css,js,output By https://twitter.com/flackrw 
	*/

	import { onMount } from 'svelte';

	let header: HTMLDivElement;
	let headerHeight: number;
	let headerShifterHeight = 0;

	function fixHeaderOffset() {
		header.style.setProperty('--computed-height', `${headerHeight}px`);

		headerShifterHeight =
			Math.min(
				header.offsetTop,
				document.documentElement.scrollHeight - window.innerHeight - headerHeight,
			) - 1;
	}

	onMount(() => {
		header.style.position = 'sticky';
		header.style.top = 'calc(var(--_computed-height) * -1 - 1px)';
		header.style.bottom = 'calc(100% - var(--_computed-height))';
		fixHeaderOffset();
	});
</script>
```

I should have mentioned I am using TypeScript.  Drop the `lang=ts` and type annotations (`:HTMLDivElement` & `:number`) in lines `13`, `20` and `21` if you opted for JavaScript instead.

Let&rsquo;s see what we have.  In lines `8` and `9` we declare the variables which we bound in the template code.  Then in line `10` we declare and initialise the `headerShifterHeight` which we also used in the markup.  Just by using `let` here, we make the variable reactive.  This means when it changes, Svelte will automatically update the style directives (as well as anything else linked) we added earlier.

The `fixHeaderOffset` function in the original implementation remains, though we have a few differences.  We got rid of the `getBoundingClientRect` call to get the header height as we now have the value bound to `headerHeight` by Svelte.  We renamed `y` to `headerShifterHeight`, just so the template code is a little clearer.  Then, remember we use style directives to set the height on the `header-shifter` element and bottom margin on the `header` element, so removed those two lines.

### Svelte` onMount`

Finally, all we have left is `onMount`.  This is a Svelte lifecycle method.  An alternative it to use Svelte actions.  We explore both in the <a aria-label="Open Svelte video blog tutorial" href="https://plus.rodneylab.com/tutorials/svelte-video-blog">tutorial on creating a Svelte video blog</a>, where we use these in lazy loading video and images.

We set the CSS style props here, instead of using style directives just because, we will not need them defined if the user has JavaScript disabled. In this instance, we want a regular `postion: relative` header, rather than a `sticky` one managed in JavaScript.  Since the `onMount` function will not load with JavaScript disabled, the CSS override here on `header.style.position` is only set with JavaScript enabled.

The `top` and `bottom` CSS properties are only relevant when JavaScript is enabled (we use `relative` positioning otherwise).  Taking that into account we set those two properties in `onMount` rather than using style directives.  Note how we use `--_computed-height` instead of `--computed-height`.  This is because, as we mentioned above, the former will have a default value, available before we first calculate and set `--computed-height`.

Finally, we put the `fixHeaderOffset` call in `onMount` because we need to access the bound JavaScript variables (`header` and `headerHeight`) when the function is invoked.  These are only available once we mount the component.  So placing the initial `fixHeaderOffset` call here avoids calling it when those variable are still undefined.

## 🙌🏽 Svelte Shy Header: Wrapup

In this Svelte shy header post. We saw some useful Svelte methods and how they relate to those you might see in plain HTML / CSS / JavaScript sites.  In particular we have touched on:

- how to use **Svelte style directives** for component styling and when another approach might be preferred,
- full code for implementing a **Svelte shy header** as a reusable component,
- how to **bind component dimensions** to JavaScript variables with Svelte.

As a next step you might consider following one of the tutorials to learn more about Svelte.  If you already know the basics, also try adding this stick header to a Svelte component library.  You might also want to take CSS custom properties to the next level, setting them in Svelte actions.  Let me know what you end up creating.  You can drop a comment below or reach out for a chat on <a aria-label="Jump into the Element Matrix chat room" href="https://matrix.to/#/%23rodney:matrix.org">Element</a> as well as <a aria-label="Mention on Twitter" href="https://twitter.com/intent/user?screen_name=@askRodney">Twitter @mention</a>

You can see the full code for this Svelte shy header project, in all its glory <a aria-label="Open the Rodney Lab Git Hub repo" href="https://github.com/rodneylab/svelte-shy-header" rel="follow noopener noreferrer">in the Rodney Lab Git Hub repo</a>.

## 🙏🏽 Svelte Shy Header: Feedback

If you have found this post useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on Twitter, giving me a mention so I can see what you did. Finally be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a aria-label="Reach out on Twitter" href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter and also <a aria-label="Contact Rodney Lab via Telegram" href="https://t.me/askRodney">askRodney on Telegram</a>. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on svelte kit" href="https://rodneylab.com/tags/sveltekit/">SvelteKit</a> as well as <a href="https://rodneylab.com/tags/seo" aria-label="See posts on search engine optimisation">Search Engine Optimisation</a> among other topics. Also <a aria-label="Subscribe to the Rodney Lab newsletter" href="https://rodneylab.com/about/#newsletter">subscribe to the newsletter to keep up-to-date</a> with our latest projects.