CSS “position: sticky” – Introduction and Polyfills

Share this article

If you’ve read the article Obvious Design always wins by Annarita Tranfici, you might agree with her statement:

People expect to see common patterns: find the main menu in the top of the page, a search box at the top-right corner, a footer at the bottom, and so on.

I agree that people expect certain components of a website to be placed in a particular location and, in my opinion, this is even more true when it comes to the main menu.

Sometimes, due to a client request or because we have determined this is the best approach, we may require that a main navigation stay visible at all times on the page, without it being fixed in place, essentially following the page content. In recent years, a lot of JavaScript-based solutions have seen the light of day because CSS alone was unable to achieve this task.

In this article we’ll discuss position: sticky, the new CSS solution to this problem.

What Problem are We Solving?

Before discussing this new value for the position property, let’s better understand what is the problem we’re trying to solve.

Let’s pretend the main menu of our amazing website is right after the header but still at the top of the page (not in a sidebar) and that it occupies all the width available. This might look like this:

See the Pen Example with no sticky menu by SitePoint (@SitePoint) on CodePen.

What we want to achieve is that when the user scrolls the page, as soon as the menu is positioned at the top of the viewport, instead of the menu scrolling out of view, it will stick at the top position — as if it had a position: fixed applied to it (only when it reaches the top of the viewport).

To achieve this with traditional code we need to add some JavaScript. We listen for the scroll event of the page and use JavaScript to change the value of the position and top properties according to the current position of the viewport. Specifically, we need to add top: 0 and position: fixed to the menu when it’s at the top of the viewport, and then revert the properties back to their defaults otherwise.

An alternative, but similar, approach is to create a class in our CSS where those values are present, and then add and remove the class as needed, with JavaScript, which might look like this:

var menu = document.querySelector('.menu')
var menuPosition = menu.getBoundingClientRect().top;
window.addEventListener('scroll', function() {
    if (window.pageYOffset >= menuPosition) {
        menu.style.position = 'fixed';
        menu.style.top = '0px';
    } else {
        menu.style.position = 'static';
        menu.style.top = '';
    }
});

Please note that this snippet doesn’t deal with older versions of Internet Explorer. If you need to deal with those browsers (poor you!), I’ll provide a few polyfill options that you can consider.

A live demo of this second step is shown below:

See the Pen position: sticky with Simply JS by SitePoint (@SitePoint) on CodePen.

But wait! Can you spot the issue this code causes? Many implementations I’ve seen, including the one we’ve developed so far, don’t take into account an important issue. When we change the position of the element to fixed, it leaves the flow of the page, so the elements below it “jump up” by a number of pixels roughly equal to the height of the element (the height of this “jump” depends on margins, borders, and so on).

A possible solution is to inject a placeholder element that is same size as the one we want to “stick” so that when we update the sticky element’s style, there won’t be a jump. In addition, we don’t want to reassign the values over and over again for no reason if the right values are already set. Finally, we want to employ the technique I described using a CSS class.

The final version of the JavaScript code is listed below:

var menu = document.querySelector('.menu');
var menuPosition = menu.getBoundingClientRect();
var placeholder = document.createElement('div');
placeholder.style.width = menuPosition.width + 'px';
placeholder.style.height = menuPosition.height + 'px';
var isAdded = false;

window.addEventListener('scroll', function() {
    if (window.pageYOffset >= menuPosition.top && !isAdded) {
        menu.classList.add('sticky');
        menu.parentNode.insertBefore(placeholder, menu);
        isAdded = true;
    } else if (window.pageYOffset < menuPosition.top && isAdded) {
        menu.classList.remove('sticky');
        menu.parentNode.removeChild(placeholder);
        isAdded = false;
    }
});

And this is the declaration block for the sticky class:

.sticky {
    top: 0;
    position: fixed;
}

The final result is shown in this next demo:

See the Pen position: sticky with JS, improved by SitePoint (@SitePoint) on CodePen.

Now that you have a good grasp of what the problem is and what’s a possible JavaScript-based solution, it’s time to embrace modernity and discuss what this position: sticky is all about.

What’s position: sticky?

If you’ve been so brave to carefully follow the previous section, you may be wondering “Why can’t the browsers do this for me?” Glad you asked!

sticky is a new value introduced for the CSS position property. This value is supposed to behave like position: static within its parent until a given offset threshold is reached, in which case it acts as if the value was fixed. In other words, by employing position: sticky we can solve the issue discussed in the previous section without JavaScript.

Recalling our previous example and using this new value we can write:

.menu {
    margin: 0;
    padding: 0;
    width: 100%;
    background-color: #bffff3;
    position: sticky;
}

And the browser will do the rest! It’s that easy.

Browser Support and Polyfills

The support for this new value is pretty poor at the moment. Here’s how things stack up for each browser:

  • Firefox 26+ – Supported by setting css.sticky.enabled to “true” under about:config.
  • Chrome 23+ – Supported by enabling “experimental Web Platform features” in chrome://flags.
  • Chrome 38(?) – The Chrome team have recently removed this feature from Blink, so it’s currently not available in Chrome Canary (version 38.x), even with the flag. You can read the bug report explaining the removal, but we suspect the feature will be re-implemented shortly, and possibly without a disruption in the stable version’s support.
  • Safari 6.1+ – Supported using the -webkit vendor prefix on the value (i.e. position: -webkit-sticky)
  • Opera 23+ – Supported by enabling “experimental Web Platform features” in about:flags.
  • Internet Explorer – No support (see status)

See position: sticky on Can I Use… for all the details.

Fortunately there a number of polyfills to choose from:

Demo

The following demo shows position: sticky in action. As mentioned, to see it work, and depending on the browser you’re using, you many need to activate a flag.

See the Pen position: sticky (pure CSS version) by SitePoint (@SitePoint) on CodePen.

Conclusion

Although the new feature doesn’t have great browser support, and you might be hesitant to polyfill it, it will degrade gracefully to the default position: static or position: fixed, if you define an alternative style using Modernizr.

If you’ve experimented with this property or know of any other polyfills, let us know in the comments.

Frequently Asked Questions (FAQs) about CSS Position Sticky

What is the difference between CSS position sticky and other CSS positions?

CSS position sticky is a hybrid of relative and fixed positioning. It allows an element to behave like a relative position within its parent until a given scroll point at which it becomes fixed. This is different from other CSS positions. For instance, the ‘relative’ position allows you to position an element relative to its normal position, while ‘fixed’ position places an element relative to the browser window, and it does not move even if the page is scrolled. The ‘absolute’ position places an element relative to the nearest positioned ancestor, and ‘static’ is the default value and positions an element according to the normal flow of the page.

Why is my CSS position sticky not working?

There could be several reasons why your CSS position sticky is not working. One common reason is that the parent element has an overflow property set to hidden, auto, or scroll. Another reason could be that the sticky element has a sibling that is taller than it, causing it to scroll off the viewport. Also, ensure that the sticky element is not a table element because CSS position sticky does not work on table elements in some browsers.

How can I make CSS position sticky work on all browsers?

While CSS position sticky is widely supported in modern browsers, it may not work in some older versions. To ensure compatibility across all browsers, you can use a polyfill. A polyfill is a script that provides the technology that you expect the browser to provide natively. One popular polyfill for CSS position sticky is Stickyfill.

Can I use CSS position sticky with other CSS properties?

Yes, you can use CSS position sticky with other CSS properties. For instance, you can use it with z-index to control stacking order, or with box-shadow to create a shadow effect when the element becomes sticky.

How does CSS position sticky behave within a scrolling container?

When a sticky element is inside a scrolling container, it will stick to the top of the container when you scroll down, and it will stop being sticky when the bottom edge of the container scrolls past.

Can I use CSS position sticky on the bottom of an element?

Yes, you can use CSS position sticky on the bottom of an element. You just need to specify a negative value for the bottom property. This will make the element stick to the bottom of its container.

How does CSS position sticky work with margins?

Margins work with CSS position sticky in the same way they work with relative positioning. The margins will move the sticky element relative to its sticky position, not its original position.

Can I use CSS position sticky for horizontal scrolling?

Yes, you can use CSS position sticky for horizontal scrolling. You just need to specify a value for the left or right property instead of the top or bottom property.

How can I control the scroll offset with CSS position sticky?

The scroll offset is controlled by the top, right, bottom, and left properties. The value you specify for these properties determines the distance from the respective edge of the viewport at which the element will start to stick.

Can I use CSS position sticky with CSS transitions?

Yes, you can use CSS position sticky with CSS transitions. However, keep in mind that the transition will only apply when the element changes from a relative position to a fixed position, not while it is sticking.

Aurelio De RosaAurelio De Rosa
View Author

I'm a (full-stack) web and app developer with more than 5 years' experience programming for the web using HTML, CSS, Sass, JavaScript, and PHP. I'm an expert of JavaScript and HTML5 APIs but my interests include web security, accessibility, performance, and SEO. I'm also a regular writer for several networks, speaker, and author of the books jQuery in Action, third edition and Instant jQuery Selectors.

learn-advanced-cssLouisLposition stickyposition sticky polyfill
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week