Simple sticky/fixed header that animates on scroll

Sticky headers are headers that remain fixed to the top of the website, and are visible even when you scroll down.

While they may seem complicated, you can actually build one yourself using some CSS and JavaScript.

This tutorial will cover:

  1. How to build a sticky header using HTML and CSS, and
  2. How to use JavaScript and CSS transitions to animate the header when you scroll down.

In a hurry? You can skip to the end and download all the source code for this tutorial from my GitHub repo here.

Let’s get started!

To begin with, we have a simple website project with the following files:

  • Index.html
  • Style.css
  • Script.js
  • Logo image for the header

Here’s what the website looks like:



Now let’s get into the code and start making our header sticky!

Step 1: Use CSS to make the header sticky

In our index.html file, the markup for the header looks like this:

    <img class="logo" src="logo-codeco.png" width="40" height="40" />
        <a class="nav__link" href="#">About</a>
        <a class="nav__link" href="#">Work</a>
        <a class="nav__link" href="#">Contact</a>

This is just a regular header, so if you scroll down the page, the header will scroll up and off out of sight.

Make the header sticky

To make the header sticky, we need to add some CSS code to the header element:

header {
    position: fixed;
    top: 0px;
    (other styles here)

The position: fixed CSS declaration allows you to fix the header to any part of the visible page.

Then the top: 0px declaration sets the header to be zero pixels from the top of the page, i.e. at the very top.

One note: setting the position property to fixed will make the header element behave very similarly to position: absolute, which takes the element out of the normal “flow” of the page.

This means that you will have to manually set a width for the element to make it go all the way across the page. We also had to set width: 100% in order for the header to display the way we wanted.

Now if we reload the page and try scrolling down, we can see that the header remains on the page and at the very top, all the time. Nice!

Add space for the header in the main content on the page

However, you can see an issue due to the header being out of that normal page flow, where elements are stacked below one another.

The rest of the content behaves as if the header isn’t even there, and gets partially hidden underneath the header:

The header has the opacity reduced to illustrate the main content underneath it.

To fix this, we need to push down the main content so it is positioned below the header (but not underneath it). Since the header is 80px tall, we can add a top margin to the main content of 80px:

section.content {
    margin-top: 80px;
    (other styles here)

After reloading the page, we can see that the content now displays nicely after the header.

We’ve now made our header sticky using CSS!

Step 2: Use JavaScript and CSS transitions to animate the header when you scroll

For our next step, we will make it just a little fancy.

We want to shrink the header height when you scroll down the page, using a smooth animation effect.

There are a couple of benefits to shrinking the header:

  • You don’t want the header to take up a ton of vertical space all the time, especially for mobile phones which have limited space.
  • Adding a subtle animation improves the user experience and gives your website an extra polish!

Before we start coding this part, let’s break down everything we want to happen into individual steps:

  1. Use JavaScript to detect when the user has scrolled a certain distance down the page.
  2. Use JavaScript again to add a new CSS class to the header element.
  3. In the CSS, add styles for this new class to shrink the header height.
  4. In the CSS for the default header, add a CSS transition so the height changes with an animated effect.

Use JavaScript to detect when the user has scrolled a certain distance down the page.

In our script.js file, we want to detect every time the user scrolls up or down the page. To do this, we will add an Event Listener to detect any scrolls. It looks like this:

// Run the checkHeader function every time you scroll
window.addEventListener('scroll', checkHeader);

This code will run the checkHeader function when scrolling.

Create and throttle a function when scrolling

However, running functions on scroll events can potentially slow down your website. This is because a scroll event will fire 15 times or more every time you swipe or spin your mouse wheel. If you have a whole lot of code in your scroll function, all those events can add up and ultimately slow down your website.

To solve this problem, you can add a throttle function to your scroll event function. This will allow you to limit, or throttle, how often the function gets triggered.

I’ve used a throttle function from the Lodash JavaScript library to only fire the checkHeader function once every 300ms:

// This function will run a throttled script every 300 ms
var checkHeader = _.throttle(() => { 
    // Run JavaScript stuff here
}, 300);

Lodash is a very popular library that works in conjunction with another JavaScript library, Underscore, to provide you with some very handy pre-built functions.

Now that we have our function set up, let’s add in more JavaScript that we will run in checkHeader.

Check scroll position

Next, we will check how far down the page the user has scrolled, by creating a variable called scrollPosition:

// Detect scroll position
let scrollPosition = Math.round(window.scrollY);

The window.scrollY value will tell you how many pixels from the top the user is scrolled. If they’re at the very top, it will be 0. The farther down they scroll, the bigger the number will get.

We are also rounding the scrollY value because it will sometimes return a decimal, and I just want to keep it a bit cleaner and use whole numbers.

Add or remove CSS class depending on scroll position

Now, we will create an IF statement to see if the user has scrolled down 100px (a bit past the header, which is 80px tall).

If they have scrolled past 100px, we will add a CSS class called “sticky” to the header. If they are above 100px, we will remove the CSS class.

Here’s the code for that:

// If we've scrolled 100px, add "sticky" class to the header
if (scrollPosition > 100){
// If not, remove "sticky" class from header
else {

A few notes on the above code: document.querySelector is an easy way to find an element, and classList.add and classList.remove are how you can add or remove CSS classes from elements.

And that’s all the JavaScript that we need for this project!

Let’s move on to more CSS…

In the CSS, add styles for this new class to shrink the header height.

Now that we’ve added that new “sticky” class, we need to create the styles for it.

To determine how much to decrease the header height, let’s look in the Inspector in Chrome:


The header has a height of 40px, and a padding of 20px on each side (top, right, bottom, left). The logo image itself is 40px, so we don’t want to decrease the height itself.

Instead, we can decrease the padding on top and bottom from 20px to 10px.

To do this, we will create a new set of CSS styles for the header element with the “sticky” class, with a reduced padding:

header.sticky {
    padding: 10px 20px;

So when the “sticky” class is added via JavaScript, the header will shrink to 60px in height. Then when the CSS class is removed, the header will go back to its original height of 80px.

Now let’s move on to the last step, and animate this height change.

Add a CSS transition so the height changes with an animated effect.

With all the code we’ve added so far, the sticky header will work functionally. Scrolling down the page will reduce the header height. However it will instantaneously jump from 80px to 60px in height, which will look jarring to the user.

Adding in a CSS transition will make the height change more gradual, and be much more pleasing to the user.

To add this in, we will add a transition property to the default header element in CSS (not the header with “sticky” class). This will ensure that whether or not the header has that “sticky” class, the height change will always be smooth.

Here is the CSS for that addition:

header {
    (other styles here)
    transition: padding 300ms ease;

The transition property can take some parameters:

  • The first parameter is which property this transition will affect. Since we are changing the padding, we will put “padding” here.
  • The second parameter is the duration of the transition, and we have put 300ms.
  • The third parameter is the speed of progression of the transition. Ease is your standard smooth progressive change.
  • There is a fourth possible parameter to set a delay on the animation, but we are not using it here.

Now with the CSS transition added, if you reload the page and scroll up and down, you’ll be able to see the header change in height nice and smooth!

Want to see a demo? Check it out on my GitHub page!

You can also download all the project files on my GitHub repo here.

In closing

Thank you for reading (or watching!) this tutorial on making a sticky header. I hope that you’ve enjoyed it and found it helpful!

Feel free to leave a comment below with your thoughts 🙂

Enjoyed this post?
Tweet me about it! 😀
Want to learn how to build a website?

I'm making a course that will teach you how to build a real-world responsive website from a Figma design!

Learn more