See the Pen position: sticky example by Dudley Storey (@dudleystorey) on CodePen.

For a time “dynamic fixed” elements were the hot web design feature: scroll a site and everything moved as expected, but when a particular element (often a menu bar, sometimes an advertisement) reached the top of the page it would fix itself in place, while the rest of the document continued to scroll underneath it. Scroll back, and the element would reattach itself to the document.

This behaviour - a dynamic hybrid between the standard position: static and position: fixed - was often added to a page with JQuery, via one of a hundred different plugins. Like many popular features, it was eventually adopted into an official specification, meaning that the effect could be achieved with native CSS, with no frameworks, scripts, or other dependencies… but in this case, the adoption process was (and continues to be) somewhat troubled.

How Things Were Meant to Be

The behavior was codified as a new CSS value: position: sticky. This, combined with a clever use of top (in the context of sticky, the distance from the top of the body at which the element will “stick” when scrolled; alternatives are left, bottom and right for scrolling in those directions) was intended to cover the range of popular use cases. The syntax, then, should be pretty simple:

#stickytest {
    position: sticky;
    top: 0px;
}

Applied to something like an image, the markup would be:

<img src="geckofoot.jpg" alt id="stickytest">
<p>Lorem ipsum…

If you test that code on a page with appropriate markup and content in a recent version of Firefox, it will work just fine. Moving to other browsers reveals “sticky’s” awkward position:

  1. Safari 6.1+ (on both desktop and mobile) support sticky with a vendor prefix. Somewhat unusually, the prefix is applied to the value, not the property, since it’s the former that’s new.
  2. There’s an added catch: in Safari sticky only works with elements that are display: block, so the CSS for this sticky image example becomes:
#stickytest {
    display: block;
    position: -webkit-sticky;
    position: sticky;
    top: 0px;
}

In addition:

  • Placing the sticky element inside a parent that has overflow: hidden applied to it will cause the sticky behaviour to fail.
  • Officially, sticky should also work with display: table, including table cells, as it’s very useful for navigating long rows of table data while keeping column headers in view. Unfortunately, browser implementation of this feature has been lacking so far.

Chrome Travails

You’ll probably find web pages that insist Chrome has support for position: sticky as an experimental option, and that was true… until the option was removed entirely in Chrome 37. The Google development team felt that keeping position: sticky was too much of a challenge in their quest to improve composition and rendering speed in the browser, so it has been placed to one side. That means we must fallback to using a polyfill to recreate the behaviour in Chrome and Internet Explorer (which has never supported the new value).

Fortunately, there are a number of options for this: the Filament Group has a JQuery solution, along with many others; my personal favorite is Oleg Korsunsky’s stickyfill, which has the option of working with or without JQuery.

The example on the StickyFill page adds the sticky behaviour as a class, but it’s more likely to affect only one element, making an id a more logical selector. In the case of this demo, after inserting the script at the bottom of the page, I’d add:

Stickyfill.add(document.getElementById('stickytest'));

Note that top, left, bottom and right for the element is measured from the body. This will include any margin already in place on the body element.

Keep It Simple

As with all new features, position: sticky has the potential for misuse. To avoid UI disasters, I would recommend the following:

  • In theory, position: sticky can be used to fix elements inside any scrolling container, as shown in the example at the top of this page. Please don’t use sticky more than once per page: we don’t need multiple content boxes acting like clogged drains. (In addition, all the polyfills available for position: fixed fail in providing this feature; the demo for this page only works because it is inside an iframe).
  • Be very careful using position: sticky at mobile screen sizes: anything that is stuck immediately takes up screen real estate, reducing the room for your content. Make sure you’re making an element sticky because it’s required or extremely helpful to do so, not because it “looks cool”. Remember that it’s easy for users to flick-scroll to the top or bottom of most pages; don’t get in the way of that behaviour.

To provide a reasonable use of sticky elements, write a @media query that counters position: sticky at smaller screen sizes:

@media all and (max-width: 600px) {
    #stickytest {
        position: static !important;
    }
}

This returns the element to normal flow in the document if the viewport is 600 pixels wide or less. You’ll need to write a similar rule in JavaScript with matchMedia if you’re using a polyfill. Re-writing and adding to the script above:

var sticky = document.getElementById('stickytest');
var screencheck = window.matchMedia("(max-width: 600px)");
Stickyfill.add(sticky);
if (screencheck.matches) {
    Stickyfill.remove(sticky);
}
  • As a rule, “stuck” elements should be the new cutting-off point for content. Don’t “stick” an element in the middle of a block, as having scrolling text appear both above and below a fixed element is very distracting, and makes the text difficult to read.
  • Similarly, try to avoid sticking elements that cut off part of a text block, forcing the user to scroll in a decreased amount of space to read a full line.
  • Ensure that the fixed element does not occupy more than the minimum expected height of the viewport. If it is taller than its container when it is “stuck”, users will never be able to see all of its content; nor are they likely to be able to read any of the other content on the page.
  • Further to this, you might want to indicate that content is truly disappearing under the stuck element: a partial transparency effect when the element is in it’s “stuck” position, such as my “frosted glass” effect, can provide a useful visual cue.

In principle, as series of elements could be provided with the same position: sticky value, effectively replacing each other at the chosen point on the screen as the page scrolls. While this can be effective, it’s also potentially very confusing to users, and should be employed carefully.

Unfortunately there’s no “stuck” event in JavaScript yet to report when an element has entered its fixed position; right now, you have to test for that independently. (Some polyfills add a class to stuck elements to make up for this lack).

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/yNxPRy