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.
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: relativeorabsolute+ az-indexother thanautoposition: fixedorstickyopacityless than 1transform,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 →