Skip to content

Z-index

Z-index utilities provide a semantic scale for controlling stacking order. Use the custom properties or utility classes to keep z-index values consistent and predictable across your application.

TokenValuePurpose
--z-behind-1Place an element behind its siblings
--z-base0Explicit stacking reset
--z-dropdown1000Dropdown menus
--z-sticky2000Sticky headers, navigation bars
--z-overlay3000Backdrops, overlays, modals
--z-toast4000Toast and snackbar notifications
--z-tooltip5000Tooltips and popovers

Each token has a matching utility class that sets z-index to the token value.

ClassSets
.z-behindz-index: var(--z-behind)
.z-basez-index: var(--z-base)
.z-dropdownz-index: var(--z-dropdown)
.z-stickyz-index: var(--z-sticky)
.z-overlayz-index: var(--z-overlay)
.z-toastz-index: var(--z-toast)
.z-tooltipz-index: var(--z-tooltip)

The classes only set z-index — compose them with position utilities like .p-fixed or .p-absolute as needed.

<!-- Sticky header -->
<header class="z-sticky" style="position: sticky; top: 0;"></header>
<!-- Custom overlay without <dialog> -->
<div class="tng-backdrop"></div>
<div class="p-fixed at-center z-overlay"></div>

Several components ship with a default z-index so they stack correctly out of the box. Utility classes can still override these values because the utilities layer has higher specificity in the cascade.

ComponentDefault z-index
.tng-backdropvar(--z-overlay)
.tng-dropdownvar(--z-dropdown)
.tng-modalvar(--z-overlay)
.tng-popovervar(--z-overlay)

When using .tng-popover as a tooltip, add the .z-tooltip utility class to ensure it stacks above other overlays.

A stacking context is a self-contained layer in which child elements are stacked relative to each other, not to elements outside the context. Understanding stacking contexts is essential for working with z-index.

Any of these properties on an element creates a new stacking context:

  • position: relative | absolute | fixed combined with a z-index value other than auto
  • isolation: isolate
  • opacity less than 1
  • transform, filter, backdrop-filter, perspective
  • will-change targeting one of the above properties
  • contain: layout | paint

A z-index: 9999 inside one stacking context cannot escape and overlap elements in a sibling stacking context with a higher or equal stacking level. This is why arbitrarily large z-index values don’t solve layering problems — they only work within their own context.

root stacking context
├── header (z-index: 2000) ← its own stacking context
│ └── dropdown (z-index: 1000) ← stacks within header, not the page
└── main (z-index: 0)
└── tooltip (z-index: 5000) ← stacks within main, not the page

In this example the tooltip at 5000 still renders below the header at 2000, because each is confined to its parent’s stacking context.

.tng-root applies isolation: isolate, creating a single top-level stacking context for the entire design system. This prevents internal z-index values from leaking into the host page and vice versa.

The browser’s top layer is separate from the z-index stacking model entirely. Elements promoted to the top layer — via <dialog>.showModal() or the Popover API — render above everything regardless of z-index. Prefer these native APIs for modals, popovers, and tooltips whenever possible.

  • Use the tokens — avoid hard-coded z-index numbers. The scale gives every layer a clear semantic name and consistent spacing.
  • Prefer the top layer — use <dialog> and [popover] for overlay patterns. They bypass z-index complexity entirely.
  • Keep stacking contexts small — the fewer nested contexts you create, the easier z-index is to reason about.
  • Don’t inflate values — if z-index: 9999 seems necessary, the real fix is usually restructuring the stacking context hierarchy.