How does z-index work?
Stacking contexts decide.
You set z-index: 9999 on your modal. It's still behind the header. You set it higher — still behind. The number isn't the problem: your modal is trapped inside a stacking context whose z-index is lower than the header's parent. This page explains stacking contexts, why 9999 doesn't win, and the three ways to fix layering bugs.
Short answer: z-index only competes with siblings inside the same stacking context. A child can never go above a parent's sibling — no matter how high its z-index. A stacking context is created by position + z-index, opacity < 1, transform, filter, isolation: isolate, and several other properties. To fix: raise the parent's z-index, remove the property creating the context, or move the element out of the trapping parent. Increasing the child's z-index makes no difference.
✅ Before and after
Why z-index: 9999 loses.
.container {
position: relative;
z-index: 1; /* creates a stacking context */
}
.modal {
position: absolute;
z-index: 9999; /* trapped inside .container */
}
.header {
position: relative;
z-index: 2; /* higher than .container /
}
Stacking order: Layer 2: .header (z-index: 2) → on top Layer 1: .container (z-index: 1) └─ .modal (z-index: 9999) → inside layer 1 The .modal's z-index: 9999 only competes with other children of .container. It cannot go above .header, because .header's layer (2) is above .container's layer (1). ❌ Modal is behind the header. ❌ Increasing z-index to 999999 does nothing. Fix: raise .container's z-index above .header's, or move .modal out of .container.
/* Move the modal out of the trapping parent */
body > .modal {
position: fixed;
z-index: 9999;
}
/* Or use a portal (React) to move it to body /
/ Or raise the parent's z-index: /
.container {
position: relative;
z-index: 10; / now above .header (z-index: 2) */
}
.modal {
position: absolute;
z-index: 9999; /* still inside .container */
}
.header {
position: relative;
z-index: 2;
}
Fix 1: Move the modal to body level. → Portal in React (createPortal) → Or restructure HTML → .modal is now a sibling of .header → Both compete in the same stacking context → z-index: 9999 wins. Fix 2: Raise the parent's z-index. → .container from 1 to 10 → Now .container's layer is above .header's → .modal inside it is above .header too → Works, but requires knowing the sibling z-indices. Fragile. ✅ Both fix the layering. ✅ The portal approach is cleaner for modals. ✅ Raising parent is fine for simpler layouts.
✅ What creates a stacking context
Any of these creates a new layer.
position + z-index
position: relative/absolute/fixed/sticky with z-index other than auto. The classic.
fixed / sticky
position: fixed or position: sticky alone — no z-index needed. Always creates a context.
opacity < 1
opacity: 0.99 creates a context. Even opacity: 0 does. Common surprise in animations.
transform / filter / clip-path
Any of these creates a context — even transform: translateX(0). Common in animated elements.
isolation: isolate
Explicitly creates a stacking context without other side effects. Use when you need it.
flex / grid item with z-index
A flex or grid item with z-index other than auto creates a context — no position needed.
⚠️ Common mistakes
The z-index mistakes that cost real time.
Ignored on static elements. Fix: add position: relative (or absolute/fixed/sticky).
The element is trapped in a lower stacking context. Fix: raise the parent, or move the child.
These create contexts, breaking z-index expectations. Fix: check the parent's transform, opacity, filter.
9999, 99999, 999999 — going up in the child doesn't help. Fix: fix the parent's layer.
Fixed creates a context; absolute doesn't unless z-indexed. Fix: understand each context's role.
Trapped in its parent's z-index. Fix: portal (React) or move HTML.
Header z-index higher than the container the modal is in. Fix: raise the modal's parent, or move modal to body.
Guessing at layers. Fix: Chrome DevTools has a "layers" panel — use it to see stacking contexts.
❓ Frequently asked
How does z-index work?
z-index controls the stacking order of positioned elements. Higher z-index wins, but only within the same stacking context. A z-index of 9999 on an element inside a stacking context with z-index: 1 cannot go above an element in a parent stacking context with z-index: 2 — the parent's z-index wins, not the child's. This is why z-index 9999 'doesn't work' — the element is trapped inside a parent stacking context.
Why doesn't z-index: 9999 work?
Because it's confined to its stacking context. A stacking context is a layer — its children cannot go higher than the layer itself, no matter their z-index. If the parent has z-index: 1, and a sibling of the parent has z-index: 2, then no child of the z-index: 1 parent can go above the z-index: 2 sibling, even with z-index: 9999. The child's z-index only competes with other children of the same parent.
What creates a stacking context?
Many things. The most common: (1) positioned element with z-index other than auto; (2) position: fixed or position: sticky regardless of z-index; (3) opacity less than 1; (4) transform, filter, perspective, clip-path, mask; (5) isolation: isolate; (6) flex or grid item with z-index other than auto; (7) will-change with a stacking-context-creating property; (8) contain: layout, paint, or strict.
Does z-index work without position?
Only in some cases. z-index on a static positioned element is ignored. z-index works on position: relative, absolute, fixed, and sticky. It also works on flex items and grid items — a flex or grid item with z-index other than auto creates a stacking context, even without position. But the classic case — z-index on a plain div — does nothing.
What is the order of stacking?
Within a stacking context, from back to front: (1) the background and borders of the element forming the context; (2) child stacking contexts with negative z-index; (3) in-flow, non-positioned block-level descendants; (4) non-positioned floats; (5) in-flow, non-positioned inline-level descendants; (6) child stacking contexts with z-index: 0 or auto; (7) child stacking contexts with positive z-index.
What is a stacking context in simple terms?
A stacking context is a group of elements that paint together as a unit. Within the group, z-index decides who's on top. Between groups, the parent group's z-index decides. Think of it as a stack of cards: each group is one card in the main stack, and its children are sub-cards within that card. You can reorder the sub-cards inside their card, but you can't slip one sub-card above another card — only the card's overall position in the main stack decides that.