DeepFrontend
Practice ArenaBlogSign in
Go Pro
DeepFrontend

Interview-grade learning paths and hands-on practice for mid-to-senior engineers. Master internals, patterns, and system architecture with runnable code.

All systems operational

Core Courses

  • -JavaScript Internals
  • -React Reconciliation
  • -TypeScript rigor
  • -Next.js Caching
  • -Node.js Event Loop
  • -System Design
  • -Web Security
  • -CSS & Page Layouts

Explore

  • Learning Paths
  • Practice Arena
  • Pricing Options
  • HTML Sitemap

Resources

  • Privacy Policy
  • Terms of Service
  • Email Support

© 2026 DeepFrontend. All rights reserved.

Expert learning environments for web engineering teams.

Home/CSS/Positioning
beginner15 min read·Updated Jul 2026

Positioning

The `position` property and its five values control how an element is removed from or kept in normal document flow, and what its offset coordinates are relative to. Getting 'relative to what' wrong is the source of nearly every positioning bug.

csspositioninglayoutz-indexstacking-context

Knowledge Check

4 questions · pass at 70%

0/4
mediummcq

1. An element has `position: absolute` with no ancestor that has a non-static position. What is it positioned relative to?


Interview Questions

3 questions

Cheatsheet

Download-ready reference
Cheatsheet
position values — flow + offset reference point:
  static    (default) normal flow, offsets ignored
  relative  normal flow, offset from ITS OWN position; establishes positioning context for children
  absolute  removed from flow, offset from nearest NON-STATIC ancestor (else viewport/initial containing block)
  fixed     removed from flow, offset from VIEWPORT — UNLESS an ancestor has transform/filter/perspective/will-change
  sticky    relative until scroll threshold, then acts fixed WITHIN its containing block

#1 positioning bug: forgetting position:relative on the intended absolute-positioning parent

z-index:
  Only compares WITHIN the same stacking context — never across contexts
  Stacking context created by: position+z-index≠auto | opacity<1 | transform | filter | will-change | isolation:isolate
  Debugging: trace ANCESTOR chain for stacking-context creators, don't just raise the number
  isolation: isolate  → create a clean stacking context with no other side effects

fixed gotcha:  ancestor transform/filter/perspective/will-change → breaks viewport-relative fixed
sticky gotcha: ANY ancestor overflow ≠ visible → can silently break sticky

inset: 0;  shorthand for top:0; right:0; bottom:0; left:0;

Use absolute/fixed for: tooltips, modals, badges, overlays (flow-independent by design)
Use Flexbox/Grid for: actual multi-item layout (don't hand-roll with absolute positioning)

Related topics

FlexboxGrid

On this page

15m
Knowledge CheckInterview QuestionsCheatsheet

Concept#

position controls two things at once: whether an element participates in normal document flow, and what its top/right/bottom/left offsets are calculated relative to. Every positioning bug traces back to getting one of those two questions wrong.

The five values#

position: static;    /* default — normal flow, offsets ignored entirely */
position: relative;  /* normal flow, offset relative to its OWN normal position */
position: absolute;  /* removed from flow, offset relative to nearest positioned ancestor */
position: fixed;     /* removed from flow, offset relative to the viewport (ignores scroll) */
position: sticky;    /* normal flow until a scroll threshold, then acts fixed within its container */

relative — offsets from itself, still takes up space#

.box { position: relative; top: 10px; left: 20px; }

The element still occupies its original space in the document flow (siblings lay out as if it weren't moved) — only its painted position shifts. This is also relative's other critical job, independent of whether you use top/left at all: it establishes a positioning context for absolutely-positioned children.

absolute — the "relative to nearest positioned ancestor" rule#

.parent { position: relative; } /* establishes the positioning context */
.child { position: absolute; top: 0; right: 0; } /* positioned relative to .parent, not the page */

An absolutely positioned element is removed from normal flow entirely (siblings act as if it doesn't exist) and its offsets are calculated relative to the nearest ancestor with a position other than static — if no ancestor has one, it falls all the way back to the initial containing block (roughly, the viewport/<html>). This is the single most common positioning bug: forgetting to set position: relative on the intended container, causing an absolutely positioned child to jump to a completely unexpected position relative to the page instead of the nearby parent.

fixed — relative to the viewport, with one major caveat#

.modal-backdrop { position: fixed; inset: 0; } /* covers the full viewport, ignores scroll */

fixed positions relative to the viewport and doesn't move on scroll — except when an ancestor has a transform, filter, perspective, or will-change property set, in which case that ancestor becomes the containing block instead of the viewport, and the "fixed" element scrolls along with it. This is a genuinely surprising, very commonly hit real bug: a modal or tooltip using position: fixed suddenly starts scrolling with the page because someone added transform to an ancestor for an unrelated animation.

sticky — the hybrid#

.header { position: sticky; top: 0; } /* normal flow, then "sticks" at top:0 once scrolled to */

sticky behaves like relative until the element would scroll past its specified offset threshold, at which point it behaves like fixed — but only within the bounds of its containing block. Once the parent container scrolls out of view, the sticky element scrolls away with it rather than staying pinned forever. Requires an explicit offset (top, bottom, etc.) to actually activate — without one, sticky behaves exactly like static.

Common sticky gotcha: if any ancestor has overflow: hidden, overflow: scroll, or overflow: auto (other than the intended scrolling container), it can silently break sticky positioning, since sticky is computed relative to the nearest scrolling ancestor, and an unrelated overflow value elsewhere in the ancestor chain can become that scrolling context unexpectedly.

z-index and stacking contexts#

.a { position: relative; z-index: 1; }
.b { position: relative; z-index: 2; } /* renders above .a */

z-index only has an effect on positioned elements (position other than static) — or flex/grid items, which have grown to also respect z-index even without an explicit position. Critically, z-index values only compare within the same stacking context — a z-index: 9999 element trapped inside a stacking context with a low z-index is still visually behind an element with z-index: 2 in a different, higher stacking context, regardless of the raw numbers. This is the source of the infamous "why won't my z-index: 99999 show on top" bug.

What creates a new stacking context (a non-exhaustive but common list): position + z-index other than auto, opacity less than 1, transform, filter, will-change, isolation: isolate. Any of these on an ancestor creates a self-contained stacking bubble — descendants' z-index values are only meaningful relative to siblings within that same bubble, not the whole page.


Common Mistakes#

1. Forgetting position: relative on the intended positioning container#

/* Wrong: .badge jumps relative to the page/viewport, not .card */
.card { /* no position set */ }
.badge { position: absolute; top: 0; right: 0; }
/* Right */
.card { position: relative; }
.badge { position: absolute; top: 0; right: 0; }

The most common positioning bug by a wide margin — always double-check the intended containing element actually has a non-static position.

2. Chasing a z-index bug by raising the number instead of finding the stacking context boundary#

.tooltip { z-index: 999999; } /* still invisible — wrong problem being solved */

If a higher z-index doesn't fix a stacking issue, the actual problem is almost always that the element is trapped inside a lower stacking context created by an ancestor (an opacity, transform, or positioned+z-index ancestor) — no child z-index value can escape its own stacking context. Trace the ancestor chain for stacking-context-creating properties instead of increasing the number further.

3. position: fixed unexpectedly scrolling because of an ancestor transform#

Covered above — if a "fixed" element starts moving with scroll, check every ancestor for transform/filter/will-change/perspective, since any of them silently redefines the fixed positioning containing block.

4. position: sticky not sticking, due to an ancestor's overflow#

If sticky isn't activating, check the entire ancestor chain (not just the immediate parent) for any overflow value other than visible — a distant ancestor's overflow: hidden set for a completely unrelated reason can silently disable sticky behavior.

5. Using absolute positioning for layout that flexbox/grid would handle more robustly#

/* Fragile: manually computed positions, breaks on content size changes */
.item-1 { position: absolute; top: 0; left: 0; }
.item-2 { position: absolute; top: 0; left: 100px; }

Absolute positioning removes elements from flow entirely — appropriate for overlays, tooltips, badges, modals (things genuinely meant to float independent of document flow), but a poor substitute for actual multi-item layout, which Flexbox/Grid handle far more robustly (responsive by nature, no manual pixel math, no overlap when content sizes change).


Best Practices#

  • Always pair position: absolute children with an explicit position: relative (or other non-static) parent, set deliberately, not accidentally inherited from elsewhere.
  • Use inset: 0 as a shorthand for top: 0; right: 0; bottom: 0; left: 0; when covering a positioned parent entirely (overlays, backdrops).
  • When debugging z-index, trace stacking contexts, not just raise numbers — inspect each ancestor in DevTools for position+z-index, opacity < 1, transform, filter.
  • (tooltips, modals, badges, sticky headers) — use Flexbox/Grid for actual multi-item layout.

Further Resources#

  • MDN — position
  • MDN — Stacking context
  • CSS-Tricks — What No One Told You About Z-Index
  • Josh W. Comeau — An Interactive Guide to CSS Position
  • web.dev — Learn CSS: Position

Reserve absolute/fixed for genuinely flow-independent elements
  • Test sticky with the real ancestor chain, not an isolated demo — overflow on any scroll-relevant ancestor can silently break it.
  • Use isolation: isolate to deliberately create a fresh stacking context when you need to contain z-index values without introducing an unwanted transform/opacity side effect.