A sticky element needs an inset and a scrolling ancestor with no overflow other than visible in between. Check those two things first.
header {
position: sticky;
top: 0;
}
The three usual causes
-
No inset. Without
top,bottom,leftorright, sticky behaves likerelative.top: 0is the one you mean for a header. -
An ancestor with overflow. Any element between the sticky one and the scroller with
overflow: hidden,auto,scrollorclipbecomes the scroll container it sticks within. If that ancestor doesn’t scroll itself, the element never moves. Find it with:let el = document.querySelector("header").parentElement; while (el) { const o = getComputedStyle(el).overflow; if (o !== "visible") console.log(el, o); el = el.parentElement; }Commonly it’s an
overflow-x: hiddenonbodyor a wrapper added to stop horizontal scroll. Sinceoverflow-x: hiddenforcesoverflow-ytoauto, that wrapper is now the scroller. Move the rule to the element that actually overflows, or useoverflow-x: clip, which doesn’t create a scroll container. -
The parent is exactly as tall as the element. Sticky sticks within its parent’s box. A header that is its parent’s only child has nowhere to travel. Give it siblings or move it up a level.
Gotchas
- In a flex column, a sticky child stretches to the container’s height by default, and an element as tall as its parent has no room to stick. Add
align-self: flex-start. - Table headers: put
stickyon theth, not thethead, for the widest browser support. z-indexis still needed if content with its own stacking scrolls over the header.