Explain CSS position values: static, relative, absolute, fixed, sticky
static: default, in normal flow, ignores top/left. relative: in flow but offset from its own position, becomes a positioning context. absolute: removed from flow, positioned against the nearest positioned ancestor. fixed: removed from flow, positioned against the viewport. sticky: hybrid — in flow until it hits a scroll threshold, then sticks.
position controls how an element is placed and what top/right/bottom/left do. There are five values.
static — the default
In the normal document flow. top/left/etc. are ignored. z-index does nothing.
relative
Stays in the flow (its original space is preserved), but you can offset it visually with top/left/etc. relative to where it would have been.
Its real superpower: it becomes a positioning context — a "containing block" for any absolute descendant.
.parent { position: relative; } /* anchor for absolute children */absolute
Removed from the normal flow — it no longer takes up space; siblings collapse as if it's gone. Positioned relative to the nearest positioned ancestor (an ancestor with position other than static). If none exists, it falls back to the initial containing block (≈ the <html> element).
.badge { position: absolute; top: 0; right: 0; } /* needs a positioned ancestor */Use for: badges, tooltips, dropdowns, overlays anchored to a parent.
fixed
Also removed from flow, but positioned relative to the viewport — it stays put when the page scrolls.
.header { position: fixed; top: 0; left: 0; right: 0; }Use for: sticky headers, floating action buttons, modals. Gotcha: a CSS transform, filter, or will-change on an ancestor makes fixed position relative to that ancestor instead of the viewport.
sticky
A hybrid. The element is in normal flow (relative) until its scroll position crosses a threshold you set (top, bottom...), at which point it "sticks" (fixed-like) within its parent's bounds. When the parent scrolls away, the sticky element goes with it.
.section-header { position: sticky; top: 0; }Use for: section headers, sticky table headers, sidebars. Gotcha: it won't work if an ancestor has overflow: hidden/auto/scroll, or if you don't set a threshold.
Summary
| Value | In flow? | Positioned against |
|---|---|---|
| static | yes | — |
| relative | yes (space kept) | itself (its normal spot) |
| absolute | no | nearest positioned ancestor |
| fixed | no | the viewport |
| sticky | yes, until threshold | scroll container / parent |
Senior framing
The senior depth here is the gotchas: relative as the anchor for absolute, fixed breaking when an ancestor has a transform, and sticky silently failing under overflow ancestors or without a threshold. Anyone can recite the definitions — knowing why position: fixed "broke" in a real layout is the differentiator.
Follow-up questions
- •Why does position: fixed sometimes position relative to a parent instead of the viewport?
- •Why might position: sticky silently not work?
- •What is a 'containing block' and 'positioned ancestor'?
Common mistakes
- •Using absolute without a positioned ancestor and getting unexpected placement.
- •Expecting sticky to work when an ancestor has overflow:hidden.
- •Forgetting sticky needs at least one threshold (top/bottom/etc.).
- •Not knowing transform on an ancestor breaks fixed positioning.
Edge cases
- •transform/filter/will-change/contain on an ancestor creates a containing block for fixed/absolute.
- •Sticky sticks only within its parent — it leaves when the parent scrolls past.
- •z-index only applies to positioned (non-static) elements.
Real-world examples
- •Dropdown menus (absolute), sticky nav (fixed/sticky), tooltips, modals, sticky table headers.