Back to CSS
CSS
easy
junior

What are the CSS position values and when would you use each?

static = default flow. relative = in flow but offsettable + new positioning context. absolute = removed from flow, anchored to nearest positioned ancestor. fixed = anchored to viewport (or transformed ancestor). sticky = relative until a threshold, then fixed within scroll container.

6 min read·~10 min to think through

position controls how an element is placed relative to its containing block and whether it participates in normal flow. Junior code uses absolute for everything; the senior signal is knowing which positioning context each value creates and the gotchas (transform containing block, sticky inside overflow:hidden).

position: static (default).

In normal flow. top/right/bottom/left/z-index are ignored. Doesn't create a positioning context for absolute children.

position: relative.

Stays in normal flow (takes its original space) but you can nudge it with top/left etc. without affecting siblings.

The big use: becomes a positioning context for position: absolute descendants. Most "absolute inside relative parent" patterns rely on this.

position: absolute.

Removed from normal flow — siblings collapse as if it weren't there. Positioned relative to the nearest positioned ancestor (any position other than static), or the initial containing block (viewport/html) if none.

Common patterns: badge in the corner of a card (position: relative parent + position: absolute badge), tooltip anchored to a trigger.

Gotcha: forget the relative parent and the absolute element pops up to the viewport — looks like a fixed element.

position: fixed.

Removed from flow. Anchored to the viewport — stays put when you scroll. Right for floating action buttons, persistent headers, modals.

The famous gotcha: fixed is contained by an ancestor with transform, filter, or will-change set. CSS spec quirk: those properties create a new containing block. So a fixed element inside a transformed parent positions relative to the parent, not the viewport. Most "why is my modal misaligned" bugs trace back to this.

position: sticky.

Hybrid: behaves like relative until the element crosses a scroll threshold, then "sticks" like fixed within its scroll container.

css
.section-header { position: sticky; top: 0; }

Sticks to the top of its scroll container as the user scrolls past it.

Sticky gotchas:

  • Containing block. Stickyness is scoped to the nearest scroll container. If no ancestor scrolls, it sticks to the viewport.
  • Parent overflow. overflow: hidden (or auto/scroll) on an ancestor cancels stickiness in older browsers. Even modern: overflow: hidden on the containing element makes sticky useless because it can't escape.
  • Height. The sticky element only sticks within its parent's height. When you scroll past the parent, the sticky element scrolls away too. This is the feature, not a bug — it's what makes sectioned sticky headers work in lists.
  • Threshold. top, bottom, left, or right defines the sticking edge. top: 0 sticks at top; top: 16px leaves a 16px gap.

Stacking contexts. position: relative/absolute/fixed/sticky + z-index (other than auto) creates a new stacking context, which clips child z-indexes. Two elements in different stacking contexts can't interleave by z-index — root context wins. isolation: isolate creates a stacking context without needing position/z-index, useful for portals.

Decision matrix.

  • Tooltip / badge / dropdown anchored to a trigger → relative parent + absolute child.
  • Floating UI persistent across scroll → fixed.
  • Header that sticks within a section list → sticky.
  • Modal overlay → fixed (and ensure no transformed ancestor).
  • Card layout → static (default flow + flex/grid).

Modern alternative: anchor positioning (anchor-name / position-anchor) is landing in browsers — Chrome 125+. Lets you position an absolute element relative to any element, not just the nearest positioned ancestor. Watch this space.

Code

css
.list { overflow-y: auto; max-height: 80vh; }
.list section h2 {
  position: sticky;
  top: 0;
  background: white;
  z-index: 1;
}
Sticky section headers in a long list
css
.card { position: relative; }
.card .badge {
  position: absolute;
  top: 8px;
  right: 8px;
}
Badge in the corner — the relative+absolute classic

Follow-up questions

  • Why does position:fixed sometimes not anchor to the viewport?
  • Why does sticky stop sticking partway down?
  • What's a stacking context?
  • How does the new CSS anchor positioning work?

Common mistakes

  • Using `absolute` without a positioned parent — pops out to the viewport.
  • Putting `fixed` inside a transformed ancestor and being surprised it doesn't anchor to viewport.
  • Setting `overflow: hidden` on a parent of a sticky element — kills the sticky.
  • Mixing z-index across stacking contexts and expecting numeric ordering.

Performance considerations

  • Sticky elements promote to a layer; cheap to keep on screen during scroll.
  • Fixed headers can avoid scroll-related repaints if marked will-change: transform.

Edge cases

  • A position:fixed iframe inside a transformed ancestor → still anchors to ancestor.
  • Sticky element with parent shorter than the element → never sticks.
  • Sticky in flexbox parent with align-items:stretch can fail to stick — set align-self: flex-start.

Real-world examples

  • Sticky table headers, sticky CTA bars, modal overlays, dropdown menus, floating chat widgets.

Senior engineer discussion

Senior signal: explaining the transform/containing-block trap with fixed, sticky's parent-bounded behavior, and stacking contexts.