Z-Index
The z-index scale that keeps sticky bars, modals, drawers and tooltips predictable.
The z-index scale assigns every layer a purpose. Components reach for the token that matches their role — a dropdown never guesses z-index: 9999 to win a stacking war.
Components reach for the token that matches their role — a dropdown never guesses z-index: 9999 to win a stacking war.
The stacking ladder
--ep-z-basePage content, static elements, default stacking.
--ep-z-dropdownDropdown menus, select popovers, autocomplete results.
--ep-z-stickySticky headers, sticky sidebars, persistent toolbars.
--ep-z-overlayFull-screen scrim backdrops, dimmed overlays.
--ep-z-modalModal dialogs, drawers, command palettes.
--ep-z-popoverTooltips, contextual popovers, floating notifications. Body-portaled components (createPortal to document.body) MUST use this tier (65) — the dropdown tier (30) loses to sticky chrome (40) in the root stacking context.
--ep-z-toastToast notifications — sits above popovers so banners stay visible during context menus.
--ep-z-skip-linkSkip-link bypass for keyboard users — must sit above every layer.
Rules of the ladder
- Never use arbitrary values like z-9999.
- Skip-links bypass everything (100) — accessibility wins over stacking wars.
- Toasts sit above popovers (70 vs 65) — banner notifications stay visible while context menus are open.
- Modals sit above overlays (60 vs 50).
- Sticky headers beat inline dropdowns (40 vs 30). A dropdown that opens inside a sticky header inherits the header's stacking context and renders correctly — UNLESS it is portaled, in which case it becomes a page-scope floating layer and loses to the sticky tier.
- Portal pattern: any ReactDOM.createPortal(..., document.body) must use the popover tier (65). Inline dropdown tier (30) is only valid for non-portaled, in-place dropdowns anchored within their host's stacking context. (Source: foundation-integrity Phase 1, 2026-08-24 — Sign-out menu was clipped under the sticky dashboard topbar at tier 30.)
Stacking contexts
A z-index token only works inside the correct stacking context. Creating a new context (via transform, opacity, filter, or will-change) traps descendants — their z-index values are relative to the context, not the page.
What to avoid
- Escalating z-index values to "win" stacking.
- Using z-index: -1 to push content behind the page.
- Setting z-index on elements that are the only child of their parent.