The five position values (static, relative, absolute, fixed, sticky) each change how an element relates to the document flow and its containing block, while z-index and stacking contexts control layering order for overlapping elements.
static (default, in flow), relative (in flow but offset), absolute (out of flow, relative to positioned ancestor), fixed (out of flow, relative to viewport), sticky (switches from relative to fixed at a scroll threshold).
Absolute elements position relative to the nearest positioned ancestor's padding box. If none exists, they use the viewport. This is why parents often need position: relative.
z-index only competes within the same stacking context. Elements with transform, opacity < 1, or positioned elements with z-index create new stacking contexts that isolate their children's z-order.
Sticky elements need a scrollable container taller than themselves and can be broken by overflow: hidden or overflow: auto on an ancestor element.
CSS positioning determines how elements are placed in the document and is a fundamental concept that interviewers test regularly. Understanding each position value, containing blocks, and stacking contexts is essential.
Elements follow the normal document flow — block elements stack vertically, inline elements flow horizontally. The top, right, bottom, left, and z-index properties have no effect on static elements.
The element remains in the normal flow (its original space is preserved), but it can be offset from its normal position using top, right, bottom, left. The offset is visual only — surrounding elements behave as if the element is still in its original position. Relative positioning creates a new containing block for absolutely positioned children and a new stacking context if z-index is set.
The element is removed from the normal flow — no space is reserved for it. It is positioned relative to its nearest positioned ancestor (any ancestor with position other than static). If no positioned ancestor exists, it is positioned relative to the initial containing block (the viewport). This is why position: relative is commonly applied to parent elements — to create a containing block for absolute children.
The element is removed from the normal flow and positioned relative to the viewport (the browser window). It stays in place during scrolling. Fixed elements always create a new stacking context. Caveat: if any ancestor has a transform, perspective, or filter property, the fixed element becomes positioned relative to that ancestor instead of the viewport.
A hybrid of relative and fixed. The element behaves as relative within its scroll container until a specified threshold is crossed (e.g., top: 0), at which point it becomes fixed within its containing block. It "unsticks" when the containing block scrolls past it. Sticky requires the containing block to be taller than the sticky element, and overflow: hidden or overflow: auto on an ancestor can break it.
The containing block determines the reference point for percentage-based sizing and positioning:
static and relative: the content area of the nearest block-level ancestorabsolute: the padding box of the nearest positioned ancestorfixed: the viewport (unless an ancestor has transform/filter)sticky: the nearest scrollable ancestorA stacking context is a three-dimensional conceptualization of HTML elements along the z-axis. z-index only competes within the same stacking context. A new stacking context is created by:
position: relative/absolute/fixed/sticky with a z-index value other than autoposition: fixed or position: sticky (always)opacity less than 1transform, filter, perspective, clip-path, or maskz-index other than autoisolation: isolateA common debugging scenario: an element with z-index: 9999 is still hidden behind another element because they belong to different stacking contexts. The parent's stacking context z-index takes precedence over the child's.
Modal overlays: Fixed positioning with inset: 0 creates a full-viewport overlay.
Tooltips and dropdowns: Absolute positioning relative to a relatively-positioned trigger element.
Sticky headers: position: sticky; top: 0; on a header element keeps it visible during scroll.
Centering with absolute: position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); centers an element within its containing block (though Flexbox is usually preferred now).
Fun Fact
The z-index property only works on positioned elements (and flex/grid children), but many developers try to apply it to static elements and wonder why it has no effect. This is consistently one of the most common CSS debugging questions on Stack Overflow.