z-index and Stacking

Who's On Top When Things Overlap

z-index and Stacking

`z-index` controls the stacking order of positioned elements. Stacking contexts make this trickier than it looks.

4 min read Level 2/5 #css#z-index#stacking
What you'll learn
  • Use `z-index` correctly
  • Recognize stacking contexts
  • Avoid the z-index escalation trap

When elements overlap, z-index decides which sits on top. Higher numbers win. But there’s a catch — stacking contexts.

The Basics

z-index only works on positioned elements (position: relative, absolute, fixed, sticky) AND flex / grid items.

.modal-backdrop { position: fixed; inset: 0; z-index: 1000; }
.modal          { position: fixed; z-index: 1001; }
.tooltip        { position: absolute; z-index: 10; }

Higher = on top.

Stacking Contexts

A stacking context is a self-contained “z-axis sandbox”. Inside it, z-index values compete. Outside it, the whole context stacks as a single unit.

You create one with:

  • position: relative or absolute + a z-index other than auto
  • position: fixed or sticky
  • opacity less than 1
  • transform, filter, will-change, isolation: isolate
  • Flex/grid items with z-index

The Trap

<div class="parent" style="z-index: 1; position: relative;">
  <div class="modal" style="z-index: 9999; position: fixed;">Modal</div>
</div>

<div class="sibling" style="z-index: 2; position: relative;">
  Other content
</div>

You’d think z-index: 9999 puts the modal on top — but no. The parent created a stacking context with z-index: 1. The sibling has z-index: 2. The MODAL competes inside its parent’s context, which is below the sibling.

The fix: move the modal out of .parent (portal it to <body>), or remove the parent’s z-index.

isolation: isolate — Deliberate Stacking Context

.card {
  isolation: isolate;
}
.card .badge {
  position: absolute;
  z-index: 1;   /* lives inside .card's context, can't escape */
}

Use this when you want a component to NOT interact with the rest of the page’s z-index.

Don’t Escalate

A bad cycle: “the dropdown is below the modal — bump its z-index from 100 to 1000. Now the tooltip is below — bump it to 10000.”

Better: pick a small set of named layers.

:root {
  --z-base: 0;
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-modal: 1000;
  --z-toast: 2000;
}

Refer to these everywhere. When you need to insert something between two values, you can — without renumbering.

Up Next

Overflow — what happens when content doesn’t fit.

Overflow →