w3tweaks.com · CSS Tutorial

CSS z-index & Stacking Contexts

See exactly why z-index sometimes works — and exactly why it doesn't.

Tab 1

Basic z-index Playground

Drag the sliders to change each box's z-index. The box with the highest value floats on top. All three boxes share the same stacking context — so z-index works as expected here.

Live Stacking Preview
A
B
C
z-index: 1
z-index: 5
z-index: 2
Key rule: z-index only works on elements with a position value other than static. An element with position: static (the default) ignores z-index entirely — set it to relative, absolute, fixed, or sticky first.
Tab 2

The Stacking Context Trap

This is where most developers get stuck. When a parent creates its own stacking context, its children are trapped inside it — no matter how high their z-index is, they can never escape to appear above elements in a higher stacking context.

BROKEN z-index: 9999 doesn't work

.parent
opacity: 0.9 ← trap
z:9999
trapped!
z:2
wins! 😱

The red box has z-index: 9999 but sits behind the cyan box with z-index: 2. Why? Its parent has opacity: 0.9, creating a stacking context that traps it.

FIXED Remove the stacking context

.parent
no opacity = no trap ✓
z:9999
works! ✓
z:2
loses ✓

Remove opacity from the parent (or set it to exactly 1), and the stacking context disappears. Now z-index: 9999 correctly floats above z-index: 2.

The core rule: z-index is always relative to its stacking context. A child with z-index: 9999 inside a context at z-index: 1 will always sit below a sibling context at z-index: 2 — no matter what. The child's z-index only battles other children within the same context.
Stacking Order — How the Browser Paints
🖼️
Layer 1
Background & borders of root element
📦
Layer 2
Block elements with negative z-index
📄
Layer 3
Block elements with no position (flow)
🎨
Layer 4
Floating elements
🔢
Layer 5
Positioned elements, z-index: 0 or auto
🚀
Layer 6+
Positioned elements, z-index: positive (higher number = on top)
Tab 3

What Creates a Stacking Context?

These CSS properties silently create a new stacking context on the element they're applied to. Once you know them, you'll never be surprised by a broken z-index again.

📌

position + z-index ≠ auto

The original trigger. Any positioned element (relative, absolute, fixed, sticky) with a z-index value other than auto.

👻

opacity < 1

Setting opacity to anything below 1 (even 0.99) creates a stacking context. The most common hidden trap developers hit.

🔄

transform ≠ none

Any CSS transform — translateX(), scale(), rotate() — creates a stacking context, even translateZ(0) used as a GPU hack.

🔍

filter ≠ none

CSS filters like blur(), drop-shadow(), and brightness() all create stacking contexts.

🧊

backdrop-filter

Adding a backdrop-filter (for glassmorphism effects) always creates a stacking context.

📦

will-change

Properties like will-change: transform or will-change: opacity create a compositing layer and therefore a stacking context.

📐

position: fixed or sticky

Both of these always create a stacking context, regardless of z-index value.

🏗️

Flex/Grid child with z-index ≠ auto

A direct child of a flex or grid container creates a stacking context when given a non-auto z-index — no position needed.

🔬 Interactive: Toggle Properties on the Parent

The yellow box always has z-index: 999. Toggle properties on the blue parent to see when it gets trapped.

.parent (z: 1)
sibling
z: 2
child
z: 999
✅ No stacking context on parent — child's z:999 beats the cyan sibling (z:2).