By Shehab Waleed, Founder, Cairo Studio 3 min read
We built a process section that pins while its steps change. The first version pinned with GSAP. When we moved it to CSS sticky, which is lighter and plays well with native scrolling on phones, the section still carried overflow: hidden from the GSAP version. With that one class in place, sticky would have done nothing at all.
Sticky sticks to a scroll container
A sticky element is positioned against its nearest ancestor that scrolls. CSS counts any element with overflow set to hidden, scroll or auto as one that scrolls, whether or not it has anything to scroll. Our section used overflow: hidden to stop wide content from spilling sideways.
.process {
overflow: hidden;
}
.process-stage {
position: sticky;
top: 0;
}So the stage sticks to the section. The section never scrolls on its own, it moves with the page, and the stage moves with it. The result looks exactly like an element that ignores sticky.
clip hides overflow without the side effect
overflow: clip cuts off overflowing content just as hidden does, but it does not make the element a scroll container. We changed the section to clip in the same commit that added sticky, and the stage looks past the section to the page.
.process {
overflow: clip;
}In Tailwind this is overflow-clip, or overflow-x-clip when only one axis should be cut. We tested both values with a sticky child in headless Chromium and in WebKit. With hidden on the parent, the sticky element had scrolled 1000 pixels out of view. With clip, it sat at the top of the viewport.
The body is a special case
We had overflow-x: hidden on the body too, and changed it to clip during the same week of scroll work. Our tests since then show that this one was probably not the cause. When the html element has the default overflow, the body hands its overflow to the viewport, and sticky keeps working in both engines.
It breaks when both html and body have a value other than visible. The body then keeps overflow for itself, becomes a scroll container, and every sticky element on the page sticks to it. That is easy to cause by accident, because scroll libraries and modal code often set overflow on html. We keep clip on the body because it is safe in every one of these cases.
Finding the ancestor
When sticky does nothing, walk up from the element and print every ancestor whose overflow is not visible.
let el = document.querySelector(".process-stage");
while ((el = el.parentElement)) {
const { overflowX, overflowY } = getComputedStyle(el);
if (overflowX !== "visible" || overflowY !== "visible")
console.log(el, overflowX, overflowY);
}Values of clip are harmless. Anything else is a scroll container. It is the same kind of search we used when a transform that moved nothing trapped our fixed elements.
