Z-Index and Stacking Contexts
Understanding the stacking order is one of the most misunderstood areas of CSS. This page explains z-index and the powerful (and often counterintuitive) concept of stacking contexts.
1. What is Z-Index?
The z-index property controls the order elements overlap each other along the Z-axis (depth into / out of the screen). Higher values appear in front.
.tooltip { z-index: 200; position: absolute; }
.modal { z-index: 300; position: fixed; }
.overlay { z-index: 250; position: fixed; }
/* Result (back to front): tooltip → overlay → modal */
[!IMPORTANT]
z-indexonly works on positioned elements (position: relative,absolute,fixed, orsticky) and on flex/grid items. Settingz-indexon a static element has zero effect.
2. Default Stacking Order (Without Z-Index)
When no z-index is set, browsers paint elements in this default order (back to front):
- Root element background and borders.
- Non-positioned elements in source order.
- Positioned elements (
position≠static) in source order.
This means a positioned element will always appear above a non-positioned one, even with no z-index set.
3. What Creates a Stacking Context?
A stacking context is a self-contained layer. Elements inside it are stacked relative to each other and move as a group relative to elements outside. A new stacking context is created by:
| Property | Value |
|---|---|
position | absolute, relative, fixed, sticky with any z-index other than auto |
opacity | Any value less than 1 |
transform | Any value other than none |
filter | Any value other than none |
will-change | Any property that creates it |
isolation | isolate |
mix-blend-mode | Any value other than normal |
contain | layout, paint, strict, or content |
display: flex/grid children | With z-index other than auto |
4. The Classic z-index: 9999 Trap
This is the most common CSS confusion:
<div class="parent-a"> <!-- Creates stacking context via opacity: 0.99 -->
<div class="child" style="z-index: 9999">I am HUGE z-index!</div>
</div>
<div class="parent-b">
<div class="sibling" style="z-index: 1">I'm small...</div>
</div>
.parent-a { opacity: 0.99; } /* Accidentally creates a stacking context! */
The sibling with z-index: 1 appears ABOVE the child with z-index: 9999 because the entire .parent-a stacking context competes with .parent-b as a unit. Within .parent-a, the child's z-index: 9999 only matters relative to other children of .parent-a.
The Fix: Isolate with isolation: isolate
.create-new-context {
isolation: isolate; /* Explicitly creates a stacking context without side effects */
}
5. Z-Index Scale System
Instead of arbitrary values, use a named z-index scale to stay organized:
:root {
--z-below: -1;
--z-base: 0;
--z-raised: 10;
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-toast: 500;
}
.sticky-header { position: sticky; top: 0; z-index: var(--z-sticky); }
.modal-overlay { position: fixed; inset: 0; z-index: var(--z-overlay); }
.modal-dialog { position: fixed; z-index: var(--z-modal); }
.toast { position: fixed; z-index: var(--z-toast); }
6. Debugging Stacking Issues
Step-by-step process:
- Open DevTools → Elements panel.
- Select the element that isn't appearing correctly.
- In the Styles panel, look for
z-indexandposition. - Walk up the DOM tree — check each parent for properties that create a stacking context (
opacity < 1,transform,filter). - Use Chrome DevTools → Layers panel (menu: More tools → Layers) to see a 3D visualization of all stacking contexts on the page.
7. WordPress-Specific Z-Index Issues
Common conflicts in GeneratePress and Bricks:
| Problem | Cause | Fix |
|---|---|---|
| Dropdown menu appears behind sections | GP header z-index too low | #site-header { z-index: 200; } |
| Bricks popup behind other elements | Stacking context on a parent section | Add isolation: isolate to offending parent |
| WooCommerce cart dropdown clipped | Overflow: hidden on container | Remove overflow: hidden or change to overflow: visible |