Stacking Contexts and z-index
Why z-index:9999 fails — stacking contexts from opacity, transform, position, and isolation, and how to debug overlay fights.
- css
- stacking-contexts
- z-index
z-index only compares elements inside the same stacking context. A child with z-index: 9999 cannot paint above a sibling of its parent if the parent itself sits under another context. Most “modal under header” bugs are stacking-context bugs, not small z-index numbers.
Docs: MDN stacking context, z-index.
When a stacking context is created
Common creators (not exhaustive):
positionnotstaticandz-indexnotautoposition: fixed/sticky(modern engines create contexts)opacityless than 1transform,filter,perspective,clip-pathother than noneisolation: isolatewill-changeof certain properties- Some
flex/gridchildren withz-index
.header {
position: sticky;
top: 0;
z-index: 10;
background: white;
}
.modal-root {
position: relative;
z-index: 1; /* whole subtree lives under header’s 10 if they’re siblings… */
}
If .modal-root is inside a transformed parent with a lower context than the header, raising the modal’s z-index only reorders within that parent.
Mental model
- Each stacking context is a private z-order universe.
- Contexts themselves are ordered by their parent’s rules.
- Descendants cannot escape their context’s “layer group.”
<div class="app">
<header class="header">…</header>
<div class="page">
<div class="card" style="transform: translateZ(0)">
<div class="dropdown" style="z-index: 9999">…</div>
</div>
</div>
</div>
The dropdown competes with other things inside .card first; the transformed .card competes with the header at a higher level.
Practical patterns
App chrome scale
:root {
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-toast: 400;
}
.dropdown { z-index: var(--z-dropdown); }
.header { z-index: var(--z-sticky); }
.modal { z-index: var(--z-modal); }
.toast { z-index: var(--z-toast); }
Use a token scale, not random 9999s.
Portals
Render modals/toasts at document.body (React portals) so they’re not trapped under transformed page sections.
isolation
.card {
isolation: isolate; /* create a clean local context for blend modes / z children */
}
Debugging checklist
- In DevTools, inspect the element — is it inside
transform/opacity/filter? - Compare the ancestors, not only the two elements.
- Check
position+z-indexpairs. - Look for sticky headers with high z-index.
- Prefer portal + known root stacking for overlays.
Interview out-loud
“z-index works within a stacking context. Transforms, opacity, filters, and positioned elements with z-index create new contexts. A huge z-index can’t escape its parent context. I use a small z-index scale, portal modals to the body, and inspect ancestors when overlays mis-order.”
Footguns
opacity: 0.99“for anti-aliasing” creating contexts.- Animating
transformon a parent of a dropdown. z-indexon non-positioned elements (ignored for comparison in many cases).- Infinite z-index inflation across teams.
- Expecting
isolationto fix everything without understanding parent order.
Modal layering recipe
.modal-backdrop {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 0.45);
z-index: var(--z-modal);
}
.modal {
position: fixed;
inset: 0;
z-index: calc(var(--z-modal) + 1);
display: grid;
place-items: center;
pointer-events: none;
}
.modal__panel {
pointer-events: auto;
}
Render both via a portal at document.body so a transformed dashboard widget cannot trap them. Focus trap and aria-modal are separate concerns — stacking only solves paint order.
Related
- Position static relative absolute fixed sticky
- transform and opacity performance
- CSS filters and backdrop-filter
- blend-mode basics
Further reading
Related guides
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.
- box-sizing border-boxWhy border-box makes width include padding and border, the global reset pattern, and the layout bugs content-box still causes.