Positioning — static, relative, absolute, fixed, sticky
The five position values aren't five variations on the same idea — each one answers a genuinely different question about what an element is positioned relative to, and most positioning bugs come from picking the wrong one for the actual relationship needed.
3 min read
static: the default, no positioning context created at all
.item { position: static; } /* the default — top/left/right/bottom have NO effect at all */static is where every element starts: it flows normally in the document, and top/left/right/bottom do absolutely nothing to a statically-positioned element — a common early confusion, since it's easy to add top: 20px to an element and see no effect at all simply because its position was never changed away from static in the first place.
relative: offsets from where the element WOULD have been
.badge {
position: relative;
top: -4px; /* nudges the element UP 4px from its normal position */
left: 8px; /* nudges it RIGHT 8px — the space it originally occupied is UNCHANGED */
}relative positioning offsets an element visually from its own normal, static position — but critically, the space it would have occupied stays reserved in the layout exactly as if it hadn't moved, so a relatively-positioned element can visually overlap its neighbors without pushing them. This is genuinely useful on its own for small visual nudges, but its real, structural importance is the next section: relative is what makes an element a positioning context for descendants.
absolute: removed from normal flow, positioned against the nearest non-static ancestor
.tooltip-container { position: relative; } /* the positioning context */
.tooltip { position: absolute; top: 100%; left: 0; } /* positioned relative to .tooltip-container */An absolutely-positioned element is taken completely out of the normal document flow — it no longer takes up space among its siblings at all — and top/left/right/bottom position it relative to the nearest ancestor that has any position value other than static (usually relative, deliberately set for exactly this purpose). If no ancestor has a non-static position, it falls all the way back to positioning against the entire page (technically, the initial containing block) — which is the classic bug where a tooltip meant to sit next to a specific button ends up pinned to the top-left corner of the whole page, because the developer forgot to set position: relative on its intended parent.
fixed: positioned against the viewport, ignores scrolling entirely
.header { position: fixed; top: 0; left: 0; right: 0; }
/* Stays pinned to the top of the VIEWPORT — scrolling the page doesn't move it at all */fixed works like absolute but positions against the viewport itself (the visible browser window), not any ancestor element — which is exactly why it's the right tool for a header or a floating action button that should stay put regardless of how far the page is scrolled. One real gotcha: a transform, filter, or will-change on any ancestor creates a new containing block that a fixed descendant positions against instead of the viewport — a genuinely surprising, hard-to-diagnose bug where a fixed element suddenly scrolls with the page because an unrelated ancestor started using transform for an animation.
sticky: relative until a scroll threshold, then fixed
.section-header { position: sticky; top: 0; }
/* Scrolls normally UNTIL it would hit the top of the viewport, then STICKS there
until its parent container scrolls out of view entirely */sticky is a genuine hybrid: the element behaves like relative (participating normally in layout, taking up its own space) until the scroll position crosses the given threshold (top: 0 here), at which point it behaves like fixed — but only within the bounds of its own containing block, unsticking again once that container scrolls fully past. This is what powers sticky table headers and sticky section labels, without any scroll-event JavaScript required at all.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What's a common early confusion with `position: static`?
2. What does `position: absolute` position an element relative to?
3. How does `position: sticky` behave differently from both relative and fixed?