Stacking Contexts and z-index
Advancedz-index only compares siblings within the same stacking context. That single rule explains why a z-index of 9999 can still render behind something set to 1.
Overview
Every developer has escalated a z-index to 9999 and watched the element stay behind its neighbour. The explanation is that z-index is not global: certain properties create a stacking context, and inside one, all children are stacked as a unit. A child with z-index 9999 inside a context whose parent is at z-index 1 can never rise above a sibling of that parent at z-index 2. Knowing which properties create a context — several of them innocuous, like transform and opacity below 1 — turns this from mystery into arithmetic.
The Rule
A worked example, because the abstract rule never convinces anyone.
<div class="a"> <!-- z-index: 1, creates a context -->
<div class="modal"> <!-- z-index: 9999 -->
</div>
<div class="b"> <!-- z-index: 2 -->
.a { position: relative; z-index: 1 }
.modal { position: fixed; z-index: 9999 }
.b { position: relative; z-index: 2 }
/* .modal renders BEHIND .b.
Because .a created a stacking context, .modal's 9999 only ranks it
among .a's descendants. The whole of .a — modal included — is then
placed at level 1, behind .b at level 2. No number inside .a can
change that. */
/* The fix is never a bigger number. It is one of:
- render the modal outside .a (a portal to document.body)
- remove whatever made .a a stacking context
- raise .a itself */
/* Within one context, the paint order is:
the element's background, negative z-index, block boxes,
floats, inline content, z-index: 0/auto, then positive z-index */What Creates One
The list, including several that surprise people.
/* Obvious */
position: relative|absolute|sticky with a z-index other than auto
position: fixed (always)
/* Not obvious — these create a context with NO z-index at all */
opacity: 0.99 /* anything below 1 */
transform: translateZ(0) /* any transform */
filter: blur(0) /* any filter */
will-change: transform
backdrop-filter
isolation: isolate /* deliberate, and useful */
mix-blend-mode
contain: paint
/* a flex or grid CHILD with a z-index */
/* This is why a card with a hover transform suddenly clips its own
dropdown, and why adding a fade animation breaks a tooltip that
worked yesterday. */
/* isolation: isolate is the deliberate version: create a context on
purpose so a component's internal z-indexes cannot leak out or be
interfered with. */
.card { isolation: isolate }
/* DevTools has a Layers panel that visualises the tree, which beats
guessing when the ancestor is five levels up. */Managing It
A small scale, and a single owner for the top layer.
/* Tokens, not magic numbers scattered through the codebase */
:root {
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-toast: 500;
}
.modal { z-index: var(--z-modal) }
/* Rules that keep it manageable:
- values in one file, nowhere else
- components never exceed 10 internally
- anything above 100 renders at the body level, via a portal
- never respond to a stacking problem with a bigger number */
/* The modern escape hatch is the TOP LAYER, which sits above
everything regardless of z-index or stacking context: */
dialog.showModal() /* native dialog */
<div popover> /* popover attribute */
/* Both also come with a ::backdrop and, for dialog, focus trapping
and Escape handling. Where support allows, they remove this whole
category of problem. */Key Points to Remember
- 1z-index only compares elements within the same stacking context, which is why a huge value can still lose
- 2transform, opacity below 1, filter and will-change all create a stacking context with no z-index involved
- 3The fix for a trapped element is a portal or removing the context, never a larger number
- 4isolation: isolate deliberately contains a component's z-indexes so they cannot leak
- 5Native dialog and the popover attribute render in the top layer, above everything, sidestepping stacking entirely
Interview Questions
Sign in to ask AriaWhy can an element with z-index: 9999 render behind one with z-index: 2?
Name three properties that create a stacking context without z-index.
What is the top layer, and how does it differ from a high z-index?
Ask Aria about Stacking Contexts and z-index
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.