In CSS, how does position: sticky differ from position: fixed for a page header?
answer
- flow participation is the real difference
- one reserves its space, one does not
- a threshold has to be crossed first
- scroll container, not always the viewport
- the parent can carry it away
basics
~20 sposition: sticky keeps the element in normal flow and pins it only after scrolling reaches the inset threshold you declared, inside its nearest scrolling ancestor. position: fixed removes the element from flow and pins it to the viewport from the start.
solid answer
~50 sA fixed element leaves normal flow immediately: it reserves no space, the content that followed it moves up to fill the gap, and it is positioned against the viewport for the entire page. A sticky element stays in normal flow, so it keeps its box and its space, and it paints exactly like `position: relative` until scrolling brings it to the threshold you set with `top`, `right`, `bottom`, or `left`. From that point the browser offsets it so it holds that threshold, but within two limits: the nearest scrolling ancestor, which is the box it sticks inside, and its own containing block, which it can never be pushed out of. So a sticky section header pins while you scroll that section and then scrolls away with it, while a fixed header stays on screen forever.
code
css · 11 lines.header-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
}
.header-sticky {
position: sticky;
top: 0;
}go deeper
Be able to say plainly that fixed leaves the flow and pins to the viewport for the whole page, while sticky stays in flow and only pins after you scroll to the offset you set.
Explain the mechanics: the inset value defines the threshold, sticking is a purely visual offset over reserved space, and the element sticks inside its nearest scrolling ancestor rather than the viewport by default.
Show judgment about which to reach for in a real layout — sticky for region-scoped headers and filter bars where you do not want to hand-compensate for lost space, fixed for elements that must survive leaving their region — and name the compensation cost of each.
Own the consistency question: a codebase that mixes both for the same visual role ends up with two different offset budgets, two overlap models, and per-page padding hacks. Argue for one convention plus tokens for header height rather than case-by-case choices.
## Two questions every positioning scheme answers When you set `position` on an element you are answering two separate questions: does this box still take up space in normal flow, and what is it offset against? `fixed` and `sticky` give opposite answers to the first and different answers to the second, which is why they look similar on screen and behave nothing alike. ## position: fixed — out of flow, anchored to the viewport `position: fixed` takes the box out of normal flow. Its former space collapses, so whatever followed it in the document moves up. The box is then offset against the viewport, using `top`, `right`, `bottom`, `left` (or the `inset` shorthand). Because it never participates in flow again, it stays exactly where you put it no matter how far the page scrolls, and it overlaps whatever scrolls underneath. The practical consequence is that you usually have to compensate for the missing space yourself, typically with padding on the page body equal to the header height: ```css .site-header { position: fixed; inset: 0 0 auto 0; /* top, right, bottom, left */ } body { padding-block-start: 4rem; /* the space the fixed header no longer reserves */ } ``` ## position: sticky — in flow, then constrained `position: sticky` does not remove the box from flow. Its space is reserved, its siblings lay out around it as if nothing special happened, and until the scroll threshold is met it renders in its normal-flow position — identical to `position: relative` with no offsets. What makes it stick is the inset value. `top: 0` says: this box's top edge may never be more than 0 from the top edge of the scrollport it lives in. While scrolling has not yet pushed it there, nothing happens. Once scrolling would carry it past that line, the browser offsets the box visually so the constraint holds. Crucially, this is a visual offset — the reserved space in flow does not move, which is exactly why nothing jumps when the element becomes stuck. ```css .section-header { position: sticky; top: 0; /* required: with no inset there is no threshold to stick to */ } ``` ## The two boundaries a sticky box lives between A sticky box is bounded on both ends. On one side is the **scroll container**: the nearest ancestor that scrolls. If some wrapper between the element and the page is a scroll container, that wrapper's scrollport is what the element sticks inside — not the viewport. If nothing between the element and the root scrolls, it sticks against the page scrollport, which is what most people expect. On the other side is the **containing block**: the sticky box can never be shifted outside it. Once the parent's edge reaches the threshold, the box stops holding position and rides the parent out of view. This is the behaviour people describe as "the sticky header stopped working halfway down" — it is the specified behaviour, and it is what makes per-section sticky headers possible at all. A fixed box has neither boundary. It answers only to the viewport. ## Choosing between them Pick `fixed` when the element must be visible for the whole page regardless of where the user is: a persistent toolbar, a floating action button, a chat launcher. Accept that you own the layout compensation for the space it no longer occupies. Pick `sticky` when the element belongs to a region and should only lead that region: a table's column headers, a section heading in a long article, a sidebar that follows while its column lasts, a filter bar above a list. You get the pinning without reserving space by hand and without the content jump when it engages. ## The things people get wrong Sticky is not "fixed with an offset". The two differ in flow participation, in what they are measured against, and in whether an ancestor can scope or silently disable them. Sticky also does nothing at all if you forget the inset, because there is no threshold to enforce — and it does nothing if an ancestor turns into a scroll container that never scrolls, because the element is then sticking inside a box whose scroll position never changes.
- If sticky keeps its space in flow, why does the page not jump when the element becomes stuck?Because sticking is a visual offset only. The box's reserved space stays exactly where normal flow put it — siblings never re-lay-out — and the browser simply paints the box shifted so the inset constraint holds. With `fixed` the space is gone from the moment the rule applies, which is why that swap does cause a jump unless you compensate with padding.
- When would you deliberately choose sticky over fixed for a site-wide header?When you want the header to hold its place in the document — no manual padding to offset lost space, no content hidden behind it on first paint, and no separate handling for short pages. Sticky also degrades gracefully: if the header never reaches its threshold, it just sits at the top in flow. Fixed is the right pick only when the element must survive leaving its own region.
- Does a sticky element create a stacking order concern like a fixed one does?Yes — sticky is a positioned element, so once it overlaps content it participates in painting order among positioned boxes. Without a `z-index`, later positioned siblings paint on top of it, which is the usual reason a stuck header ends up underneath the content scrolling past it.
Fixed is a sign bolted to the window of a moving train: it never moves relative to you. Sticky is a passenger who walks forward until they reach the front of their own carriage, then travels along with the carriage.
saying these in an interview costs you the question
- Says sticky is just fixed with a scroll offset
- Claims sticky removes the element from normal flow
- Thinks sticky always pins relative to the viewport
- Forgets that sticky needs an inset value to engage
- Says a stuck element re-flows its siblings when it pins