Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

overflow: hidden breaks position: sticky. overflow: clip doesn't.

Someone reports a bug: the "jump to section" bar at the top of a long settings page used to stick to the top of the viewport while you scrolled. Now it doesn't. It just sits there in normal flow like position: sticky was

Someone reports a bug: the "jump to section" bar at the top of a long settings page used to stick to the top of the viewport while you scrolled. Now it doesn't. It just sits there in normal flow like position: sticky was never applied.

You open devtools. The CSS is untouched — position: sticky; top: 0;, same as it's been for months. No typo, no overridden rule, nothing crossed out in the inspector. The element is exactly as sticky as it was yesterday. Except it isn't sticking.

The actual change shipped in a different file entirely: a shared <Card> component picked up overflow: hidden last week, to stop a decorative background image from spilling past its rounded corners. Nobody touched the settings page. Nobody touched the sticky bar. And yet here we are.

The rule nobody reads until it bites them

position: sticky doesn't stick to the viewport by default. It sticks to its nearest scroll container — the closest ancestor whose overflow isn't visible. Usually that's the page itself, so sticky behaves exactly like you'd expect: pinned to the window as you scroll.

But "nearest ancestor with non-visible overflow" doesn't check whether that ancestor actually scrolls. It just checks the computed overflow value. Set overflow: hidden anywhere between the sticky element and the page — for any reason, clipping an image, trimming a shadow, stopping an accidental horizontal scrollbar — and that element becomes the sticky container instead of the viewport.

.card {
  border-radius: 16px;
  overflow: hidden; /* so the thumbnail respects the rounded corner */
}

.card .jump-bar {
  position: sticky;
  top: 0; /* now relative to .card, not the page */
}

.card never scrolls — its content fits. So the sticky bar's "scroll container" never moves, which means the bar never has a reason to detach from its normal spot and pin anywhere. Visually: it just looks broken. There's no console warning, no deprecation notice, nothing that points back to a border-radius fix in an unrelated component.

Try it before you keep reading: open your own devtools on any sticky element that's misbehaving and check getComputedStyle on each ancestor for overflow. It's almost always hidden, and it's almost always there for a reason that has nothing to do with scrolling.

The fix that doesn't ask you to restructure anything

overflow: clip looks, on paper, like a quieter synonym for hidden. It clips content at the element's padding edge the same way. The difference is in what it refuses to become: the CSS Overflow spec defines clip so that it never creates a scroll container. There's no scrollport to establish, so nothing can be scrolled into it — not by the user, not by element.scrollTop = 40 in your own code. hidden still lets that second one through, which is exactly the mechanism it uses to qualify as scrollable in the first place.

.card {
  border-radius: 16px;
  overflow: clip; /* clips the same, isn't a scroll container */
}

Swap the one keyword and the sticky bar starts looking straight past .card again, up to the real scroll container — the page. Nothing else in the component changes. No wrapper <div>, no position: relative hack, no JavaScript recalculating offsets on scroll.

Two details worth knowing before you reach for it everywhere:

  • It's independent per axis. overflow-x: clip; overflow-y: visible; does what you'd want. Try that with hidden and the browser forces the other axis to auto for you, which is its own long-running source of surprise scrollbars.
  • overflow-clip-margin lets clipped content bleed a few pixels past the box before it's cut — useful when a focus ring or a shadow would otherwise get sheared off exactly at the edge.

The one thing overflow: clip can't do that hidden can is be scrolled at all — if that container is genuinely meant to scroll its own content (a chat panel, a code block with a max-height), hidden paired with a real scrollbar is still the right tool. clip is for the much more common case: an element that clips for layout reasons and was never supposed to be a scroll container in the first place.

Play with the before/after below — flip .card between hidden and clip and watch the sticky bar inside it react in real time.

🎮 Try it yourself

▶️ Open the interactive playground →

Runs right in your browser — poke at it and watch the concept react live.

The part worth remembering

overflow: hidden was never just "clip this." It was always "clip this, and also: this now scrolls, even if you never intended that and nothing inside it ever moves." Every position: sticky bug report that starts with "it worked until I touched a totally unrelated component" is worth checking against exactly that rule before you touch the sticky declaration at all.

overflow: clip separates the two meanings that hidden always bundled together. If you only wanted the first one, it's the one to reach for now.

Next time a sticky element goes dead for no visible reason, what's the first ancestor you check — and how far up the tree did the real culprit turn out to be last time?

🧠 Test yourself

Think it clicked? Take the 7-question quiz →

Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.

🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.

Thanks for reading! Let's stay connected:

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.