Two layers, same pattern as color: a core scale of raw, meaningless
rungs (--z-0 through --z-90), and a
semantic scale of intent-named aliases onto those rungs
(--z-dropdown, --z-modal, --z-popover, and so
on). The jumps between rungs are deliberately uneven โ they were set to match stacking
values already in use across components (dropdowns at 1000, overlays near 9999, etc.),
so introducing the token scale didn't change any existing stacking behavior.
Stacking Order
Each panel below is rendered at its real semantic z-index, so you can see what sits
above what.
sticky navbar
--z-sticky (100)
dropdown panel
--z-dropdown (1000)
modal
--z-modal (2000)
drawer overlay
--z-overlay (9998)
tooltip / toast
--z-popover (9999)
command palette
--z-max (10000)
Core Scale
| Token |
Value |
--z-0 |
0 |
--z-10 |
10 |
--z-20 |
20 |
--z-30 |
30 |
--z-40 |
100 |
--z-50 |
1000 |
--z-60 |
2000 |
--z-70 |
9998 |
--z-80 |
9999 |
--z-90 |
10000 |
Semantic Scale
| Token |
Resolves to |
Use for |
--z-sticky |
var(--z-40) ยท 100 |
Sticky in-flow elements, e.g. a sticky navbar |
--z-dropdown |
var(--z-50) ยท 1000 |
Floating panels anchored to a trigger: dropdown, select, combobox, date/time
pickers, suggestions
|
--z-modal |
var(--z-60) ยท 2000 |
App-level modal backdrop + content |
--z-overlay |
var(--z-70) ยท 9998 |
A secondary overlay above a modal, e.g. a drawer backdrop |
--z-popover |
var(--z-80) ยท 9999 |
Content that must float above everything in normal flow |
--z-tooltip |
var(--z-80) ยท 9999 |
Same layer as popover โ tooltips |
--z-toast |
var(--z-80) ยท 9999 |
Same layer as popover โ toast notifications |
--z-max |
var(--z-90) ยท 10000 |
Absolute top layer: command palette, snackbar, lightbox, submenus |
When to Use It โ and When Not To
Component code should reference the semantic tokens
(var(--z-dropdown)), not the raw --z-N rungs, so the
meaning of a layer is visible at the call site.
Small z-index values used purely for local stacking tricks inside a single component
are not part of this scale and don't need a token.
If you're layering a decorative pseudo-element above its own container, ordering a badge
dot over an avatar, or doing any z-index trick where everything being stacked lives
inside one component's own DOM subtree โ and the value is 20 or smaller โ leave it as a
plain integer. These values never interact with another component's stacking context, so
giving them a global name would be noise, not clarity. Reach for a global token only
when the element needs to stack above or below content belonging to a
different component.
/* Local stacking trick โ stays a plain small integer, no token needed */
.avatar-status-dot {
position: absolute;
z-index: 1;
}
/* Cross-component global layer โ use the semantic token */
.dropdown-menu {
position: absolute;
z-index: var(--z-dropdown);
}
.modal-backdrop {
position: fixed;
z-index: var(--z-modal);
}
Do / Don't
/* Don't โ raw global-scale number, meaning not visible at the call site */
.toast {
z-index: 9999;
}
/* Don't โ reaching past the semantic layer for no reason */
.toast {
z-index: var(--z-80);
}
/* Do */
.toast {
z-index: var(--z-toast);
}