Understanding z-index and Stacking Contexts
- CSS Basics
- Selectors & Specificity
- Box Model & Units
- Typography & Text Styling
- Colors & Color Spaces
- Positioning & Stacking Context
- Flexbox
- Grid
- Grid Advanced
- Responsive Design
- Container Queries
- Pseudo-classes & Pseudo-elements
- Modern Selectors
- CSS Custom Properties
- CSS Functions
- Transitions & Animations
- Transforms
- Forms & Input Styling
- CSS Architecture & Methodologies
- Accessibility in CSS
- Performance & Optimization
- View Transitions & Modern APIs
- Cascade Layers & Scope
What z-index Does
z-index controls which element appears on top when two positioned elements overlap. A higher value sits in front of a lower one - but only among elements that are being compared within the same stacking context.
.modal {
position: fixed;
z-index: 1000;
}
.overlay {
position: fixed;
z-index: 999;
}
/* .modal renders in front of .overlay */The Rule People Forget
z-index only has an effect on an element with a position value other than static (or on flex/grid items). Setting z-index on a statically positioned element does nothing at all.
What a Stacking Context Is
A stacking context is a self-contained group where z-index values are only compared against each other, not against elements outside the group. Certain CSS properties create a new stacking context, including a positioned element with a z-index, opacity less than 1, and transform.
Why a Huge z-index Sometimes Still Loses
.parent {
position: relative;
z-index: 1; /* creates a new stacking context */
}
.child {
position: relative;
z-index: 9999; /* huge, but trapped inside .parent's context */
}
.sibling-of-parent {
position: relative;
z-index: 2; /* only needs to beat .parent's z-index of 1 */
}Here, .child's z-index of 9999 only matters inside .parent. Once .parent itself is compared to .sibling-of-parent, it is .parent's z-index of 1 that counts - so .sibling-of-parent still wins, no matter how large .child's number is.
Practical Advice
Keep z-index values organized in a small, deliberate scale (for example 10, 20, 30 for layered UI elements, and a much higher reserved range for modals/tooltips) rather than guessing ever-larger numbers when something does not appear on top.