By Aurelio De Rosa

CSS “position: sticky” – Introduction and Polyfills

By Aurelio De Rosa

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) { = 'fixed'; = '0px';
    } else { = 'static'; = '';

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'); = menuPosition.width + 'px'; = menuPosition.height + 'px';
var isAdded = false;

window.addEventListener('scroll', function() {
    if (window.pageYOffset >= && !isAdded) {
        menu.parentNode.insertBefore(placeholder, menu);
        isAdded = true;
    } else if (window.pageYOffset < && isAdded) {
        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:


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.


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.

  • This link illustrates why I think position:sticky sucks:

    See how the scrolled-to-anchor position is **under** the position:sticky overlay? Same happens if you manually scroll to positions in JS.

    What I want, and what I think would be so much better, is if when the item becomes “sticky”, it actually reserves the viewport space, such that the rest of the scrolling content is now sort of in a virtual “iframe” set at the bottom of, rather than underneath, the sticky navbar.

    That way, if you have an scrolled-to-anchor or JS-set position, it would be relative to the visible viewport without the sticky item overlay it.

  • By there way, the code in the javascript solutions are very robust, great use of booleans

  • LouisLazaris

    You’re right. Of course, this applies to position: fixed in general, and has always been a problem, and is not specific just to “sticky”.

    The problem is, for the browser to “reserve” the viewport space, it would have to correctly identify the height of that element. Probably that’s not hard to do, but it would seem odd if the browser did that, calculating an arbitrary height value. I don’t know, maybe it’s easy to do in the browser, but it doesn’t feel like the kind of thing that browser’s should be doing, as helpful as it might be.

    • Yo Dirkx

      Of course the browser should do this. What else is the point of anchors on a page with a sticky anything? I’m amazed the browser doesn’t do this. It would be the one thing we can’t fake easily. No, JS isn’t good enough, there are too many issues and scenario’s with anchors and scroll positions.

  • Great point. Kudos.

    I want to say I saw a javascript snippet for this. But don’t hold me to that. In any case, it certainly would make sense to have some sort of offset for anchors. Odd that after all this time there’s not.

  • Depending on which elements are being used as link targets, this can be corrected with a little CSS. For example, on one of my sites each h2 element is a link target, and the stylesheet sets top padding and corresponding negative top margin. This means the top of the element is moved further up the page relative to its content, and so the sticky navigation covers the additional padding rather than the content.

    • Yo Dirkx

      Negative margin will always screw something else up. The browser is the only one that knows enough to do this well. Or I’m bad at CSS, which is not unlikely. Could you get your method working here? I can’t. Would be very truely awesome if this were possible with CSS only!

  • Alex

    Elements ‘jump up’ can be fixed through CSS. Just specify position ‘absolute’ to the navigation and padding or margin top which is equal to nab height

  • Mate Brkić
  • Aurelio De Rosa


    Yes, Louis and I are aware of this message. In fact, in the article you can read the note regarding the compatibility with Chrome 38.

    • Mate Brkić

      Sorry, I skipped through the last part of article. Anyway great article.

  • Aurelio De Rosa

    If you intend to set “position: absolute” to the element that should stick, this won’t make it. You need to apply “position: fixed” to simulate the effect of “sticky”.

  • This is easily offset by switching the margin/padding on the headers and p-tags, so the anchor elements have a top-padding sufficient to push them below the sticky header. Not an ultimate solution, but if you knew you were doing sticky, would be easy to set-up from the start.

  • visitor

    in Firefox 32.0.3 there s no need to adjust css.sticky.enabled in about:config, it works out of the box (at least on Ubuntu, but I don’t think it behaves diffently on other platfoms)

  • NickToye

    Unfortunately I get this error:

    Uncaught TypeError: Cannot read property ‘getBoundingClientRect’ of null

  • Unfortunately I get this error:

    Uncaught TypeError: Cannot read property ‘getBoundingClientRect’ of null

  • TJ

    Wait what… this positioning only works for scroll on body to stick elements to the top of the page? What about parts of the application having its own scroll? What about specifying the offset at which to stick/unstick? what about specifying scroll directions?

    If this is all there is to it’s its a very limited and not well thought feature IMO

  • Tanuju Venkatesh

    How do I change the same to bottom, i.e I wanted a div to be fixed and to stick the child div to parent div when it ends..

Get the latest in Front-end, once a week, for free.