Blog
3 min read

z-index Not Working? The Real Reasons (Stacking Contexts Explained)

Set z-index: 9999 and the element is still behind something? z-index only works on positioned elements and only within its stacking context. The four common causes, how to spot a stacking context, and the fixes — including portals for modals and dropdowns.

You've given your dropdown z-index: 9999 and it's still hiding behind the card below it. Bigger numbers won't fix this. z-index follows rules that aren't obvious, and once you know them the fix is usually one line.

What z-index does

When elements overlap, z-index decides which appears on top. Higher numbers sit above lower ones. Simple — with two big conditions.

Cause 1: the element isn't positioned

z-index has no effect on elements with the default position: static.

.dropdown { z-index: 100; }                       /* ignored */
.dropdown { position: relative; z-index: 100; }   /* works */

Any of relative, absolute, fixed or sticky will do. (Children of flex and grid containers are an exception: z-index works on them without position.) (CSS position explained)

Cause 2: it's trapped in a stacking context

This is the one that confuses everyone.

A stacking context is like a sealed layer. Everything inside it is stacked within that layer, and the whole layer is then stacked against its siblings as one unit. A child with z-index: 9999 can never rise above something outside its parent's layer if the parent's layer is lower.

<div class="header" style="position: relative; z-index: 1">
  <div class="dropdown" style="position: absolute; z-index: 9999">…</div>
</div>
<div class="content" style="position: relative; z-index: 2">…</div>

The dropdown is stuck under .content, because its parent .header (z-index 1) is below .content (z-index 2). The 9999 only competes with other things inside .header.

Fix: raise the parent (.header { z-index: 3 }), or move the dropdown out of it.

What creates a stacking context

Not just z-index. These all create one — often by accident:

  • position (other than static) with a z-index value
  • position: fixed or sticky
  • opacity less than 1
  • transform, filter, backdrop-filter, perspective, clip-path, mask
  • isolation: isolate
  • will-change set to one of those properties
  • contain: paint (or layout)

A parent with transform: translateY(0) from an animation library or opacity: 0.99 from a fade can silently trap every child's z-index. Inspect the ancestors.

Cause 3: overflow is clipping it

Sometimes the element is on top, but it's being cut off by an ancestor with overflow: hidden (or auto). The dropdown ends at the edge of the card. That's clipping, not stacking — no z-index will fix it.

Fix: remove overflow: hidden from the ancestor, or render the element outside it (see portals below).

Cause 4: it's a different element on top

Use the browser's DevTools: right-click the spot where your element should be → Inspect. You'll see what's actually there — sometimes an invisible overlay or a transparent full-width element is sitting on top. (Browser developer tools)

Chrome and Edge DevTools also have a Layers panel that shows stacking visually.

The best fix for modals, dropdowns and tooltips: portals

Floating UI is easiest when it's rendered at the end of <body>, outside every card, transform and overflow. In React that's a portal:

import { createPortal } from 'react-dom'

return createPortal(<div className="modal">…</div>, document.body)

Component libraries like shadcn/ui and Radix do this automatically for dialogs, popovers and dropdowns — which is why they rarely have z-index problems. (What is shadcn/ui?)

Keep z-index values sane

Instead of an arms race of 999, 9999, 99999, define a small scale and stick to it:

:root {
  --z-dropdown: 10;
  --z-sticky: 20;
  --z-modal: 30;
  --z-toast: 40;
}

Checklist

  1. Is it positioned (or a flex/grid child)?
  2. Does an ancestor create a stacking context (transform, opacity, filter, z-index)?
  3. Is an ancestor's overflow clipping it?
  4. What does DevTools say is actually on top?
  5. For floating UI: use a portal.

EasySpawn runs your app with a live preview next to Claude Code, so you can ask "why is this dropdown hidden?" and watch the fix land. See how it works or join the waitlist.

Related: CSS Position Explained · Browser Developer Tools for Beginners · How to Center a Div · Flexbox vs Grid

Keep reading