CodeOath
← All posts
HTML & CSS70 min total · 18 parts

CSS Fundamentals: The Box Model, Specificity, Positioning, and Layout

Contents — Part 10 of 18: Positioning and Stacking Contexts
Part 10 of 18 · ~2 min

Positioning and Stacking Contexts

The position property changes how an element participates in layout and how its offset properties (top/right/bottom/left) behave.

ValueBehavior
static (default)Normal document flow; top/left/z-index have no effect
relativeStays in normal flow, but can be nudged with top/left/etc., relative to where it would have been
absoluteRemoved from normal flow entirely, positioned relative to the nearest ancestor with a non-static position (or the initial containing block, if none exists)
fixedPositioned relative to the viewport; stays put when the page scrolls
stickyBehaves like relative until a scroll threshold is crossed, then "sticks" like fixed within its containing block

The most common absolute pattern is pairing it with a relative parent used purely as a positioning anchor:

.card {
  position: relative; /* becomes the reference point for .badge below */
}
.badge {
  position: absolute;
  top: 8px;
  right: 8px;
}

z-index only has an effect on elements whose position is something other than static — a common source of "I set z-index: 999 and nothing happened" bugs is simply forgetting to also set position: relative (or another non-static value) on the element.

Stacking contexts

z-index values don't compete globally — they only compete within the same stacking context. Several CSS properties create a brand-new stacking context for an element and everything inside it, which isolates its z-index values from the rest of the page:

  • Any positioned element with a z-index other than auto
  • opacity less than 1
  • transform, filter, or will-change set to a value other than none
  • A Flex or Grid container, for its own positioned children in some cases
.parent-a { position: relative; z-index: 1; }
.child-a  { position: relative; z-index: 9999; } /* still can't escape .parent-a's stacking context */

.parent-b { position: relative; z-index: 2; } /* .parent-b sits above .parent-a as a whole */

.child-a's z-index: 9999 only wins within .parent-a — it can never appear above .parent-b's content as long as .parent-b's own stacking context (z-index: 2) is above .parent-a's (z-index: 1). This is the real explanation behind "I set an insanely high z-index and it still renders behind something" — the element is trapped inside a lower stacking context created by one of its ancestors.