Problem Solved

CSS · z-index

Home › Courses › CSS › Problems › z-index
🎨 Concept · 3 min read

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.

trapped-child.css
.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.
fixed.css
/* 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.

1. z-index without position

Ignored on static elements. Fix: add position: relative (or absolute/fixed/sticky).

2. z-index: 9999 doesn't work

The element is trapped in a lower stacking context. Fix: raise the parent, or move the child.

3. Not knowing about opacity/transform

These create contexts, breaking z-index expectations. Fix: check the parent's transform, opacity, filter.

4. Escalating the numbers

9999, 99999, 999999 — going up in the child doesn't help. Fix: fix the parent's layer.

5. Mixing fixed and absolute carelessly

Fixed creates a context; absolute doesn't unless z-indexed. Fix: understand each context's role.

6. Modal not at body level

Trapped in its parent's z-index. Fix: portal (React) or move HTML.

7. Header and modal fight

Header z-index higher than the container the modal is in. Fix: raise the modal's parent, or move modal to body.

8. No DevTools inspection

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.

🔗 Related

← All CSS problems
Problem solved · CSS