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:

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:

The Browser Painting Order Within a Context

Within a single stacking context, elements are painted from back to front in the following sequence:

  1. The background and borders of the element establishing the context.
  2. Descendant stacking contexts with negative z-index values (ordered lowest to highest).
  3. Non-positioned, block-level descendants in the normal document flow.
  4. Non-positioned floating descendants.
  5. Non-positioned inline descendants (including text runs and inline elements).
  6. Positioned descendants with z-index: auto or z-index: 0.
  7. Descendant stacking contexts with positive z-index values (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.