What is z-index?
When elements overlap, z-index controls which one appears on top. Elements with a higher z-index are drawn in front of elements with a lower one. It only applies to positioned elements (anything other than position: static).
Basic z-index
<style>
.box {
position: absolute;
width: 100px;
height: 100px;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.back {
background: steelblue;
top: 20px;
left: 20px;
z-index: 1;
}
.front {
background: crimson;
top: 50px;
left: 70px;
z-index: 2;
}
</style>
<div style="position: relative; height: 160px;">
<div class="box back">Back</div>
<div class="box front">Front</div>
</div>z-index Requires Positioning
z-index has no effect on an element with the default position: static. It only works on elements positioned with relative, absolute, fixed, or sticky.
Stacking Contexts
z-index values are not compared globally — they are compared within the same stacking context. Certain properties (like opacity < 1, transform, or filter) create a new stacking context, which can trap child z-index values inside it, isolated from the rest of the page.
Common Stacking Context Triggers
| Property | Effect |
|---|---|
| position + z-index | The most common way to create a new stacking context |
| opacity less than 1 | Creates a new stacking context |
| transform (any value but none) | Creates a new stacking context |
| filter | Creates a new stacking context |
A Common z-index Pitfall
A child with z-index: 9999 can still appear behind another element if its parent has a lower z-index and both are in separate stacking contexts — the child can never escape its parent’s stacking context.
Best Practice
Keep a small, documented scale of z-index values across your project (e.g. 10 for dropdowns, 100 for modals, 1000 for tooltips) instead of arbitrary large numbers, to avoid unpredictable stacking battles.