How Does z-index Work in Stacking Contexts?
The CSS z-index property determines the
three-dimensional rendering order of overlapping elements along the
virtual z-axis, perpendicular to the screen surface. However,
z-index does not operate across an entire page as a single
global priority list; instead, its rendering behavior is strictly scoped
to the element's local stacking context. This article explains the
relationship between z-index values and stacking contexts,
outlines the triggers that generate new stacking environments, and
details how the browser calculates final layer hierarchy.
Understanding the Z-Axis and Basic Layering
In standard web layouts, elements flow along two primary axes: horizontal (x-axis) and vertical (y-axis). When visual elements overlap due to positioning, negative margins, or transforms, the browser must determine which layer sits in front of another. This depth calculation occurs along the z-axis.
By default, in-flow elements stack based on source code order:
elements declared later in the HTML document render on top of earlier
ones. Applying a non-static position (such as
relative, absolute, fixed, or
sticky) paired with an integer z-index
explicitly sets an element's depth relative to its peers. A higher
numerical value pulls an element closer to the viewer, while a lower or
negative value pushes it backward.
The Role of Stacking Contexts
The critical constraint of z-index is that it only
compares elements within the same stacking context. A
stacking context is a self-contained three-dimensional conceptual space
formed by a specific HTML element.
Stacking contexts operate in a strict tree hierarchy:
- Parent Isolation: All child elements contained inside a stacking context are locked within that parent's layer tier.
- Bounded Hierarchy: An element with
z-index: 9999nested inside a lower-priority stacking context will always render behind an element withz-index: 1located inside a higher-priority stacking context. - Atomic Treatment: The browser renders the entire child hierarchy of a stacking context as a single visual unit when positioning it relative to sibling stacking contexts.
Common Triggers That Create Stacking Contexts
A stacking context is automatically initialized by the root
<html> element. Additional local contexts are
instantiated whenever an element matches any of several modern CSS
rules, including:
- Positioned elements (
relative,absolute,fixed, orsticky) with az-indexvalue other thanauto. - Direct children of a
display: flexordisplay: gridcontainer with az-indexvalue other thanauto. - Elements with an
opacityvalue strictly less than1. - Elements with CSS
transform,filter,perspective,clip-path, ormaskapplied with values other thannone. - Elements using
isolation: isolate, which explicitly creates a new context without requiring coordinate repositioning. - Elements using
mix-blend-modevalues other thannormal. - Elements with
will-changereferencing properties that would trigger context creation upon mutation.
The Browser Painting Order Within a Context
Within a single stacking context, elements are painted from back to front in the following sequence:
- The background and borders of the element establishing the context.
- Descendant stacking contexts with negative
z-indexvalues (ordered lowest to highest). - Non-positioned, block-level descendants in the normal document flow.
- Non-positioned floating descendants.
- Non-positioned inline descendants (including text runs and inline elements).
- Positioned descendants with
z-index: autoorz-index: 0. - Descendant stacking contexts with positive
z-indexvalues (ordered lowest to highest).
Managing Complex Layer Conflicts
Unintended stacking conflicts typically occur when developers attempt
to fix layering issues by assigning arbitrarily high numbers (such as
z-index: 999999) to descendant nodes. If the target element
is trapped inside an ancestor context that sits lower than a sibling
component (such as a sticky navigation bar or modal overlay), increasing
the descendant's z-index produces no visual change.
Resolving these structural bottlenecks requires identifying the
ancestor element establishing the conflicting context and adjusting the
stacking order at that parent level, or using modern isolation rules
like isolation: isolate to prevent styles from leaking into
unintended child scopes.