Codectionary / Developer documentation / CSS

z-index and Stacking Context

z-index controls which element appears on top when elements overlap, but only works on positioned elements (anything other than static). Stacking contexts group elements together for z-index comparison purposes - z-index values only compete within the same stacking context, which is why a very high z-index can sometimes mysteriously fail to appear on top.

Syntax

z-index: number;

Examples

z-index in an Ordinary Block Layout

A common mistake - z-index silently does nothing without position set.

/* Demo setup: sizing, structure or neutral surfaces. */
.behind,.in-front{width:14rem;height:5rem;padding:1rem;color:white}.behind{background:#b45309}.in-front{background:#166534;margin-top:-2rem;margin-left:3rem}

.behind {
  z-index: 999; /* does not establish stacking for this ordinary static block; flex/grid items differ */
}

.in-front {
  position: relative; /* now z-index actually applies */
  z-index: 999;
}
  1. The examples overlap so painting order can be observed.
  2. A positioned block can use z-index in this ordinary block layout.
  3. Flex and grid items can also use z-index without positioning, so the rule is not universally limited to positioned elements.
HTML used by this example
<div class="preview-stack"><div class="behind">Static block</div><div class="in-front">Positioned block</div></div>

The Stacking Context Trap

Why a huge z-index sometimes still loses to a smaller one.

/* Demo setup: sizing, structure or neutral surfaces. */
.preview-stack{min-height:12rem}.parent-a,.parent-b{width:16rem;height:6rem;color:white;padding:1rem}.parent-a{background:#7c3aed}.parent-b{background:#166534;margin-top:-3rem;margin-left:4rem}.child{background:#b45309;padding:1rem;width:18rem}

.parent-a {
  position: relative;
  z-index: 1; /* creates a new stacking context */
}
.parent-a .child {
  position: relative;
  z-index: 9999; /* only competes WITHIN parent-a's stacking context */
}

.parent-b {
  position: relative;
  z-index: 2; /* parent-b beats parent-a entirely, so ALL its children render above,
                 regardless of how high a z-index .child has inside parent-a */
}
  1. The two parents create separate stacking contexts.
  2. The child’s large value competes inside parent A, not directly against parent B.
  3. Where they overlap, parent B can cover the child because its parent context is above parent A.
HTML used by this example
<div class="preview-stack"><div class="parent-a"><div class="child">Child: z-index 9999 inside parent A</div></div><div class="parent-b">Parent B: higher outer context</div></div>

Parameters and values

  • z-index (number | auto): Stacking order among elements in the same stacking context

Best practices

  • Remember z-index only has an effect on positioned elements (position other than static) - set position: relative (or another value) first
  • Understand that properties like opacity < 1, transform, and filter also create new stacking contexts, which can trap z-index values unexpectedly
  • Use a documented, limited scale of z-index values across a project (like 10, 20, 30, 100 for modals) rather than arbitrary huge numbers, to keep stacking predictable
  • When a high z-index "does not work," the real cause is almost always an unexpected stacking context on an ancestor, not the z-index value itself being too low

Browser compatibility

chrome
Yes
firefox
Yes
safari
Yes
edge
Yes

These are the existing reference’s support labels, not tested minimum versions. Individual values and APIs can have different support. Check the linked specification before choosing a browser target.

CSS specifications (modules)

Specifications & further reading