Position: stuck; — and a way to fix it

The shortcomings of position: sticky; when working with overflow.

Dannie Vinther
UX Collective

--

Sticky elements or frozen panes — as you might know them by — have been around forever in software programs — especially in rows/columns based applications such as Microsoft Excel.

Sticky elements are also widely used in mobile (native) applications and in web design. Such elements help users keep a sense of perspective when sorting through long lists or keeping track of their order details when shopping online.

Dribbble Shot from Jackson T Owens — https://dribbble.com/shots/5250649-Cart-Checkout-Flow

In this article, I want to explore the shortcomings of position: sticky; — specifically how we might be able to think up a creative technique when working with overflows, which causes frustration when working with the native solution.

My motivation for writing about this comes from reading the comments of Elad Shechter’s article. I therefore wanted to “chip in” with my experience working with position: sticky;.

NB! I will not be diving into how position: sticky; works. If you want to learn the basics you might want to read Elad Shechter’s introductory article at https://medium.com/@elad/css-position-sticky-how-it-really-works-54cd01dc2d46

Let’s dive in.

The old way

Telling an element to behave both stickily and static in the browser window is somewhat challenging. Before (modern) browsers introduced a native solution, we had to resort to faking sticky behaviors (e.g., for headers, sidebars etc.) using javascript and position: fixed;. Javascript listens for scroll events and the element’s position in the document, and once the math adds up you add a class of, say, “sticky”, which will appoint position: fixed; to the element causing it to stick according to your instructions.

A rather complex and carefully choreographed technique for something (that ought to be) fairly straightforward. This technique comes with a few and (somewhat) obvious pitfalls:
a) the element is not participating in the document flow causing a jump once it becomes sticky, and
b) potential performance degradation as a result of continuous repaints and callbacks due to scroll event (you might be able to handle performance issues by using techniques such as throttling, debouncing (https://www.sitepoint.com/throttle-scroll-events/) or even Intersection Observer (https://css-tricks.com/design-v17/#article-header-id-3)).

The sticky way

The convenient thing about position: sticky; is that the browser is doing all the heavy lifting for you. The element seamlessly participate in the flow of the document as well as sticking to the window once you scroll past it without causing a sudden jump.

You can accomplish this with four lines of code:

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

Easy!

However, position: sticky; can become a tricky acquaintance — especially if we introduce overflows.

Issues with overflows

Say we want an overflowing table of columns and rows with sticky headings on a page. We want the headings to stick while scrolling on the document window, and we want to be able to scroll horizontally within the overflowing container.

However, when working with overflows you might find that your sticky element isn’t so sticky after all, which may cause some frustration. The browser doesn’t seem to be respecting position: sticky; once we add overflow to the mix.

The issue with overflows is…

that a sticky element “sticks” to its nearest ancestor that has a “scrolling mechanism” (created when overflow is hidden, scroll, auto, or overlay), even if that ancestor isn’t the nearest actually scrolling ancestor. This effectively inhibits any “sticky” behavior. (From MDN Web Docs)

The behavior is replicated in this codepen from Valtteri Laitinen:

This is exactly what we want, but the headings aren’t sticking when we vertically scroll past the table. If the parent container is equipped with an overflowing property, then we’ve got trouble. Once you remove overflow, the headings stick as expected (try toggling the checkbox).

So how might we cope with this?

Add a fixed height

After digging around the internet for answers, I found a useful suggestion: specifying a height on our overflowing container.

In this simplified version, we’ve set a fixed height to our overflowing container:

While this does make the headings stick, it only works when scrolling within the container and not on the document window.

Unfortunately, this is not what we want.

Suggested solution

First of all, let’s separat the headings from the table. We want the headings to be contained within their own div (“headers”), so that we can work with them separately. Let’s assign position: sticky; to this div, and let’s add another div inside called “scroller”, so that the content can scroll horizontally.

<!-- HTML -->
<div class="wrap">
<div class="headers"> <div class="scroller"> <div class="track">
<div class="heading">Heading 1</div>
</div>
...
</div>
</div> <!-- headers -->
.../* Styles */
.headers {
position: sticky;
top: 0;

z-index: 1;
}
.scroller {
overflow: auto;
}
GIF Example

Now the headings stick when we scroll on the document window. But we are not quite there yet. Notice that we are now able to scroll both the headings and the table individually; when scrolling horizontally, the headings doesn’t move alongside the table columns.

In order to fix this, we need to introduce a tiny script (946 bytes) to be able to sync up the scrolling of the two containers. By adding the class .syncscroll alongside a name attribute, we are able to achieve synced scrolling behavior of the two divs.

In order to prevent a scrollbar on the headings-container we also need to add:

.scroller {
overflow-x: hidden;
}

This gives us the desired result:

GIF Example

And here’s a real-world example:

I haven’t been able to find a pure CSS solution to sync up the scrolling containers.

A note on browser support

Apart from some small issues with thead in tables, browser support for position: sticky; is quite good. However, our old friend Internet Explorer doesn’t recognize it, so if you want your “sticky” element to work in older browsers consider using a polyfill such as stickyfill.

Also, remember that Safari need a prefix (position: -webkit-sticky).

That’s all — my two cents on the matter. I hope you enjoyed it.

Let me know in the comments if you know of another technique or if you just want to comment on this article.

--

--