ESC

Type to search the knowledge base.

Position: static, relative, absolute, fixed, sticky

Containing blocks, stacking, sticky constraints, and when position is the right tool vs flex/grid.

beginner5 min read
  • css
  • position
  • layout
  • sticky
  • absolute

position controls how an element participates in layout and what its offsets (top / right / bottom / left / logical insets) refer to. Most page structure should still be flex or grid. Position is for overlays, sticky chrome, badges, and deliberate removal from normal flow.

Docs: MDN position, MDN Understanding z-index, CSS Positioned Layout.

The five values

Value In normal flow? Offsets relative to
static (default) Yes Offsets ignored
relative Yes (space reserved) Element’s normal position
absolute No Nearest positioned ancestor (or initial containing block)
fixed No Viewport (usually); transformed ancestors change this
sticky Hybrid Scrollport of nearest scroll ancestor, within containing block

“Positioned” means position other than static. That definition matters for absolute containing blocks.

static and relative

.badge-host {
  position: relative; /* establish containing block for absolute children */
}
.badge {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(40%, -40%);
}

relative without offsets looks like static but:

  1. Creates a containing block for absolute descendants.
  2. With offsets, visually moves the box while layout still uses the original space (gap left behind).
  3. Participates in stacking contexts when z-index is not auto.

Use relative on a host, not on every element “just in case.”

absolute

Removed from normal flow. Siblings pack as if it isn’t there. Size defaults can shrink-to-fit content unless you set dimensions or stretch with opposing insets (inset: 0 fills the containing block).

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.5);
}

Containing block for absolute: nearest ancestor with position ≠ static, or certain other cases (e.g. transform, filter, perspective, contain, will-change of those — they create containing blocks even when position: static). That last rule is a frequent “my fixed/absolute stuck to the wrong box” bug.

fixed

Stays put relative to the viewport while the page scrolls — typical for floating action buttons, cookie banners, or a global nav. Caveats:

  • A transformed (or filtered, etc.) ancestor makes fixed behave more like absolute relative to that ancestor.
  • Mobile browser chrome and 100vh quirks still apply for full-viewport fixed UIs.
  • Fixed UI can obscure content and focus — reserve space or ensure focus management for dialogs (dialog).
.toast-region {
  position: fixed;
  inset-block-end: 1rem;
  inset-inline-end: 1rem;
  z-index: 50;
}

sticky

Sticky is relative until a threshold, then stuck within its containing block like fixed — until the containing block scrolls away.

.table-wrap {
  overflow: auto; /* sticky is relative to this scrollport */
  max-height: 20rem;
}
th {
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}

Requirements people miss:

  1. A scroll ancestor (overflow not visible on an ancestor, or the viewport).
  2. An inset (top, bottom, …) — without it, sticky has nothing to stick to.
  3. Room to stick: if the sticky element’s parent is exactly as tall as the element, it never “travels.” Parent must be taller (e.g. sticky th inside a tall table, or sticky sidebar inside a long column).
  4. overflow: hidden on an ancestor often kills sticky for descendants.
  5. Parent backgrounds: when stuck, content scrolling underneath needs an opaque sticky background or content shows through.

Sticky headers for whole pages usually use top: 0 on a header inside document flow — simpler than fixed + spacer if the header’s natural height is fine.

Stacking and z-index

z-index only applies to positioned elements (and flex/grid items). It works within a stacking context. New contexts appear from positioned + z-index, opacity < 1, transform, filter, and more. A child with z-index: 9999 cannot escape a parent context that sits under a sibling.

/* Both create contexts; painting order is between .a and .b first */
.a { position: relative; z-index: 1; }
.b { position: relative; z-index: 2; }
.a .popup { z-index: 9999; } /* still under .b’s tree if .b is above .a */

Debug stacking by finding which ancestor created the context, not by inflating magic numbers. See also stacking contexts.

Position vs layout systems

Problem Prefer
Nav with logo left, actions right Flex
Page columns and regions Grid
Badge on avatar corner Absolute + relative host
Header that stays while body scrolls Sticky (or fixed + padding)
Full-screen modal Prefer native dialog / top layer; absolute/fixed as fallback

Absolute for entire page layouts is how we got unmaintainable CSS in 2012. Don’t go back.

Performance note

Animating top / left invalidates layout more often than animating transform. For motion, prefer compositor-friendly properties — same story as the rendering pipeline and Core Web Vitals (INP/jank). Sticky/fixed chrome can also contribute to CLS if it appears late without reserved space.

Interview angle

Define each of the five values in one line. Explain absolute containing blocks and the transform/fixed gotcha. Explain sticky’s need for top + scroll ancestor + tall enough parent. Mention stacking contexts so z-index: 9999 isn’t your only strategy.

Live coding: avatar with notification badge; sticky table header inside an overflow container; prove sticky breaks when parent is overflow: hidden.

Further reading

Related guides