CSS stacking contexts create isolated z-index layers where child elements cannot escape their parent's stacking order, regardless of their z-index values.
Key Principle (as of CSS specification updated November 2025):
A stacking context is created by positioned elements (position: absolute, relative, fixed, sticky) with a z-index value other than auto. Once created, all descendant elements are contained within that stacking context and their z-index values are evaluated only relative to siblings within the same context.
Example Problem:
.parent-1 {
position: relative;
z-index: 1; /* Creates stacking context */
}
.child-1 {
position: absolute;
z-index: 9999; /* High value, but trapped in parent-1's context */
}
.parent-2 {
position: relative;
z-index: 2; /* Creates stacking context */
}
.child-2 {
position: absolute;
z-index: 1; /* Low value, but parent-2 has higher stacking order */
}
<div class="parent-1">
<div class="child-1">I have z-index: 9999</div>
</div>
<div class="parent-2">
<div class="child-2">I have z-index: 1, but I'm on top!</div>
</div>
Result: child-2 appears above child-1 because parent-2 (z-index: 2) has higher stacking order than parent-1 (z-index: 1). The child's z-index of 9999 is irrelevant outside its parent's stacking context.
Stacking Context Creation (all methods as of 2024):
- Root element (
<html>) - Positioned elements with z-index ≠ auto
- Elements with opacity < 1
- Elements with transform ≠ none
- Elements with filter ≠ none
- Elements with perspective ≠ none
- Elements with isolation: isolate
- Elements with will-change specifying any property that creates stacking context
- Flex/grid items with z-index ≠ auto
Correct Hierarchy Example:
/* Top-level stacking contexts */
.modal-backdrop { z-index: 100; position: fixed; }
.modal-content { z-index: 101; position: fixed; }
/* Within modal-content, these values are relative to each other */
.modal-header { z-index: 1; position: relative; }
.modal-body { z-index: 2; position: relative; }
Debugging Stacking Issues:
- Identify which ancestor creates a stacking context (look for positioned + z-index, opacity, transform, etc.)
- Check if parent's z-index is lower than competing elements
- Use browser DevTools to inspect computed stacking contexts
- Consider moving element outside problematic stacking context (via DOM restructuring)
Solution Pattern:
If you need an element to appear above all others regardless of parent stacking:
.always-on-top {
position: fixed; /* Escapes parent positioning */
z-index: 9999;
/* Or use CSS isolation */
}
Or use the isolation property to intentionally create a stacking context:
.isolate {
isolation: isolate; /* Creates stacking context without positioning */
}
Best Practice:
Manage z-index values systematically using CSS variables:
:root {
--z-dropdown: 100;
--z-modal: 200;
--z-tooltip: 300;
--z-notification: 400;
}