If you’ve ever set position: sticky on a sidebar menu or table of contents and watched it just… not stick, you’re not alone.

Why CSS position: sticky Stops Working (And How overflow on html/body Breaks It)

In this article I’ll walk through a real case I ran into, including:

  • The actual reason sticky positioning fails
  • How to properly diagnose it in DevTools
  • Why overflow on html and body was the culprit — and how to fix it

with a reproducible step-by-step breakdown.


The common misunderstanding about position: sticky

You might have CSS that looks perfectly correct, like this:

css
.category-toc {
  position: sticky;
  top: 100px;
}

And still run into the same complaints:

  • position: sticky is set, but nothing happens
  • top is defined
  • No overflow: hidden on any parent element — or so you think

If it’s still not sticking, there’s a good chance the declaration itself is fine, but something structural is silently killing it.


What to check first (diagnosing with DevTools)

Open DevTools in Chrome (or Edge), go to the Console tab, and run:

js
const el = document.querySelector('.category-toc');

getComputedStyle(el).position
getComputedStyle(el).top
getComputedStyle(el).overflow

[!NOTE] If this is your first time pasting into the console, you may need to type “allow pasting” manually before Chrome lets you paste.

What a healthy result looks like

txt
position: "sticky"
top: "100px"
overflow: "visible"

If these values look right, the sticky declaration itself isn’t the problem.

If it’s still not sticking after confirming this, the next suspect is elsewhere.

The most common root cause: overflow on html / body

In my case, the culprit was the overflow settings on html and body.

Checking the console revealed something like this:

js
{
  scrollingElement: 'html',
  htmlOverflow: 'hidden auto',
  bodyOverflow: 'auto'
}

Which effectively meant:

  • html: overflow-x: hidden; overflow-y: auto
  • body: overflow: auto

In other words, two separate scroll containers were active at once.

Why overflow on html/body breaks sticky

position: sticky calculates its position relative to:

  • The nearest scrolling ancestor, or
  • The viewport, if there isn’t one

But when:

  • html is scrollable
  • body is also scrollable
  • And there’s a wrapper element on top of that

…the browser can’t reliably determine which container sticky should be anchored to, and the practical result is an element that appears to ignore sticky positioning entirely.

The fix: let html handle scrolling, and return body to normal flow

The most reliable fix is this:

css
html {
  overflow-x: hidden;
  overflow-y: auto;
}

body {
  overflow: visible;
}

Key points

  • Consolidate vertical scrolling onto html only
  • Don’t set overflow: auto on body
  • overflow-x: hidden for preventing horizontal scroll is fine to keep

The moment this change went in, the sticky element that had refused to work started functioning immediately.


Testing it live in the console first

Before touching your actual CSS, you can test this behavior temporarily in the console:

js
document.documentElement.style.overflowY = 'auto';
document.documentElement.style.overflowX = 'hidden';
document.body.style.overflow = 'visible';

If sticky starts working after running this, you’ve confirmed overflow is the cause.

Watch out for these common regressions

Sticky positioning can break again if any of the following show up later in your codebase:

  • Modal implementations that leave html { overflow: hidden } on permanently
  • Layout code that sets body { overflow: auto } for unrelated reasons
  • A height: 100vh wrapper combined with its own overflow control

If you need to lock scrolling while a modal is open, it’s safer to keep overflow: auto as the default state and only switch to hidden while the modal is active:

css
html {
  overflow-y: auto;
  overflow-x: hidden;
}

.modal-open html {
  overflow: hidden;
}

Summary: what to check when sticky won’t stick

  • Don’t assume you’re safe just because position and top look correct
  • Check overflow on html and body, not just the immediate parent
  • Make sure there’s only one active scroll container on the page

More often than not, sticky positioning issues come down to where the page is actually scrolling from rather than a mistake in how sticky itself is written.

Hopefully this saves someone else the same debugging session.