CSS z-index

Stacking contexts, layering, and why z-index: 9999 doesn’t always work

How z-index Works

z-index controls the stacking order of elements that overlap. Higher values appear on top. But there are two critical rules most people miss:

  1. z-index only works on positioned elements — the element must have position: relative, absolute, fixed, or sticky. On position: static (the default), z-index is completely ignored.
  2. z-index is scoped to stacking contexts — elements can only compete with siblings in the same stacking context. A child can never appear above or below elements outside its parent’s stacking context.

Basic Example

.behind { position: relative; z-index: 1; } .in-front { position: relative; z-index: 2; }
z-index: 1
z-index: 2
z-index: 3

Stacking Contexts Explained

A stacking context is like a z-index scope. Elements inside a stacking context are layered relative to each other, but the entire context is treated as a single unit in the parent’s stacking order.

/* Parent A creates a stacking context with z-index: 1 */ .parent-a { position: relative; z-index: 1; } .child-a { position: relative; z-index: 999; } /* high, but trapped inside parent-a */ /* Parent B has a higher stacking context */ .parent-b { position: relative; z-index: 2; } .child-b { position: relative; z-index: 1; } /* appears ABOVE child-a! */

In this example, .child-a has z-index 999 but still appears below .child-b (z-index 1). Why? Because .parent-a (z-index 1) is below .parent-b (z-index 2). The child cannot escape its parent’s stacking context.

What Creates a Stacking Context

Many CSS properties create new stacking contexts (not just z-index):

This is why adding opacity: 0.99 or transform: translateZ(0) can “fix” z-index issues — they create a new stacking context.

z-index Scale for Your Project

Define a scale and stick to it. This prevents z-index wars where numbers keep escalating to 99999:

:root { --z-base: 0; --z-elevated: 1; /* cards, slight lifts */ --z-dropdown: 10; /* dropdowns, popovers */ --z-sticky: 100; /* sticky headers */ --z-overlay: 1000; /* modals, drawers */ --z-toast: 10000; /* toasts, notifications (always on top) */ } .modal { z-index: var(--z-overlay); } .dropdown { z-index: var(--z-dropdown); } .toast { z-index: var(--z-toast); }
Layerz-indexExamples
Base0Page content
Elevated1Cards, raised elements
Dropdown10Menus, popovers, tooltips
Sticky100Fixed headers, sticky sidebars
Overlay1000Modals, drawers, lightboxes
Toast10000Notifications, error banners

isolation: isolate

Creates a stacking context without any visual side effects. Use it to scope z-index within a component:

.component { isolation: isolate; /* creates stacking context */ } .component .layer-1 { position: relative; z-index: 1; } .component .layer-2 { position: relative; z-index: 2; } /* These z-index values cannot leak outside .component */

This is the cleanest way to create a stacking context. No position, no opacity hacks.

Debugging z-index

  1. Open DevTools, select the element, check if position is set
  2. Walk up the DOM tree — find which ancestors create stacking contexts
  3. Chrome DevTools: in the Layers panel, you can see all stacking contexts visually
  4. If an element “won’t go above” something, its parent’s stacking context is likely too low

Frequently Asked Questions

Why does z-index 9999 not work?
Two common reasons: (1) The element has position: static, which ignores z-index. Add position: relative. (2) The element is inside a stacking context with a lower z-index than the element you are trying to cover. No child z-index can escape its parent’s stacking context.
What creates a new stacking context?
Many things: positioned elements with z-index, opacity < 1, transform, filter, clip-path, isolation: isolate, will-change, and flex/grid children with z-index. This is why adding transform: translateZ(0) can fix z-index issues.
What z-index values should I use?
Define a scale: 1 (elevated), 10 (dropdowns), 100 (sticky), 1000 (modals), 10000 (toasts). Use CSS variables. Never use arbitrary values like 9999 — they lead to escalation wars.

Related Tools