Z-Index Guideline
Why This Guideline?
Uncontrolled z-indexes lead to hard-to-debug overlapping issues. This guideline defines mandatory layers for all UI elements and prevents "z-index wars" between components.
Layer Overview
The entire UI is divided into six core layers. Each layer has a dedicated z-index range and defines a specific level of visual hierarchy. Every element on the page belongs to exactly one of these layers.
Base (0–9)
The base layer contains all regular page elements that are part of the normal document flow. This is the default layer for everything that does not require special stacking behavior. Elements here may have decorative backgrounds (e.g. gradients, background images), be slightly elevated (e.g. a card with a shadow) or have short-lived input overlays (e.g. a combobox), but they never visually compete with navigation, dialogs or notifications.
Rule of thumb: If it scrolls with the page and does not block the rest of the UI, it belongs in the Base layer.
Sticky (10–19)
The sticky layer contains persistently visible elements that remain on screen while the user scrolls. These elements need to stay above all base layer content at all times. The sticky layer may also contain its own sub-elements such as input overlays or raised elements (e.g. a search combobox inside a navbar).
Rule of thumb: If it sticks to the viewport and is always visible, it belongs in the Sticky layer.
Panel (20–29)
The panel layer contains large overlay elements that slide in or expand over the page content, such as off-canvas navigations, burger menus or mega-menus. Unlike modals, panels are typically triggered by navigation interactions and do not necessarily require the user to explicitly dismiss them before continuing. They sit above sticky elements but below modal dialogs.
Rule of thumb: If it slides or expands over the page content as part of navigation, it belongs in the Panel layer.
Modal (30–39)
The modal layer contains dialogs and overlays that intentionally block interaction with the rest of the UI. A backdrop is used to visually separate the modal from the content below. Elements inside a modal (e.g. a combobox) use modal-specific sub-layer tokens to stay within the modal's stacking context.
Rule of thumb: If it requires the user's attention and blocks the rest of the UI, it belongs in the Modal layer.
Notification (40–49)
The notification layer contains short-lived messages that need to be visible regardless of what is currently open on the page — including modals. It sits above all primary content layers but below popovers, so a popover triggered from within a notification (e.g. a help-tooltip on a snackbar) can still appear on top.
Rule of thumb: If it's a system-generated, short-lived message, it belongs in the Notification layer.
Popover (50–59)
The popover layer contains transient elements anchored to a trigger and invoked by a direct user interaction (right-click, hover, click, keyboard shortcut). Despite the name, this layer covers all anchored floating elements — including context menus, tooltips, popovers, dropdown menus rendered into a portal and drag previews. Because these elements are often triggered from inside other layers (e.g. a context menu inside a modal, a tooltip on a toast), they must sit above everything else. Nothing except z-force should ever appear above this layer.
Rule of thumb: If it's a transient element triggered by a direct user interaction and anchored to a trigger, it belongs in the Popover layer.
Layer System
| Layer | Sub-Layer | Class | Value | Description | Examples |
|---|---|---|---|---|---|
| Base | Backdrop | z-base-backdrop |
4 | Decorative background elements | Gradients, Background Images |
| — | z-base |
5 | Regular page elements | Cards, Sections | |
| Raised | z-base-raised |
6 | Slightly elevated elements within the page flow | Floating Buttons, Tooltips in Content | |
| Input | z-base-input |
7 | Short-lived overlays of input components on the page | Combobox, Select, Datepicker | |
| Sticky | Backdrop | z-sticky-backdrop |
9 | Semi-transparent background behind a sticky element | Sticky Backdrop |
| — | z-sticky |
10 | Persistently visible navigation elements | Navbar, Sidebar | |
| Raised | z-sticky-raised |
11 | Slightly elevated elements within a sticky context | Dropdown Menu in Navbar | |
| Input | z-sticky-input |
12 | Short-lived overlays of input components inside a sticky element | Combobox inside a Navbar | |
| Panel | Backdrop | z-panel-backdrop |
19 | Semi-transparent background behind a panel | Burger Menu Backdrop |
| — | z-panel |
20 | Large overlay navigation elements | Burger Menu, Off-Canvas Nav, Mega-Menu | |
| Raised | z-panel-raised |
21 | Slightly elevated elements within a panel | — | |
| Input | z-panel-input |
22 | Short-lived overlays of input components inside a panel | Combobox inside a Burger Menu | |
| Modal | Backdrop | z-modal-backdrop |
29 | Semi-transparent background behind a modal | Modal Backdrop, Drawer Backdrop |
| — | z-modal |
30 | Modal dialogs | Dialog, Confirmation, Lightbox | |
| Raised | z-modal-raised |
31 | Slightly elevated elements within a modal | Floating Buttons inside a Modal | |
| Input | z-modal-input |
32 | Short-lived overlays of input components inside a modal | Combobox inside a Modal | |
| Notification | Backdrop | z-notification-backdrop |
39 | Semi-transparent background behind a notification | Notification Backdrop |
| — | z-notification |
40 | Short-lived notifications | Toast, Snackbar | |
| Raised | z-notification-raised |
41 | Slightly elevated elements within a notification | — | |
| Input | z-notification-input |
42 | Short-lived overlays of input components inside a notification | Combobox inside a Notification | |
| Popover | Backdrop | z-popover-backdrop |
49 | Semi-transparent background behind a popover | Popover Backdrop (rare) |
| — | z-popover |
50 | Anchored, user-triggered floating elements | Context Menu, Tooltip, Popover, Dropdown | |
| Raised | z-popover-raised |
51 | Slightly elevated elements within a popover | Submenu of a Context Menu | |
| Input | z-popover-input |
52 | Short-lived overlays of input components inside a popover | Combobox inside a Popover | |
| Force | — | z-force |
99 | Only allowed with justification and ticket | — |
Implementation
Tailwind Projects
The tokens are defined in tailwind.config.js and are mandatory for all developers. Changes only after team alignment.
// tailwind.config.js
module.exports = {
theme: {
extend: {
zIndex: {
// Base
'base-backdrop': '4',
'base': '5',
'base-raised': '6',
'base-input': '7',
// Sticky
'sticky-backdrop': '9',
'sticky': '10',
'sticky-raised': '11',
'sticky-input': '12',
// Panel
'panel-backdrop': '19',
'panel': '20',
'panel-raised': '21',
'panel-input': '22',
// Modal
'modal-backdrop': '29',
'modal': '30',
'modal-raised': '31',
'modal-input': '32',
// Notification
'notification-backdrop': '39',
'notification': '40',
'notification-raised': '41',
'notification-input': '42',
// Popover
'popover-backdrop': '49',
'popover': '50',
'popover-raised': '51',
'popover-input': '52',
// Exceptions
'force': '99',
}
}
}
}
Use the Tailwind classes directly in your markup:
<!-- ✅ Tailwind -->
<div class="z-modal isolate">
<div class="z-modal-input">
...
</div>
</div>
Non-Tailwind Projects
Use the numeric values from the layer table directly as CSS custom properties or inline styles. We recommend defining them as CSS custom properties in a central location:
:root {
/* Base */
--z-base-backdrop: 4;
--z-base: 5;
--z-base-raised: 6;
--z-base-input: 7;
/* Sticky */
--z-sticky-backdrop: 9;
--z-sticky: 10;
--z-sticky-raised: 11;
--z-sticky-input: 12;
/* Panel */
--z-panel-backdrop: 19;
--z-panel: 20;
--z-panel-raised: 21;
--z-panel-input: 22;
/* Modal */
--z-modal-backdrop: 29;
--z-modal: 30;
--z-modal-raised: 31;
--z-modal-input: 32;
/* Notification */
--z-notification-backdrop: 39;
--z-notification: 40;
--z-notification-raised: 41;
--z-notification-input: 42;
/* Popover */
--z-popover-backdrop: 49;
--z-popover: 50;
--z-popover-raised: 51;
--z-popover-input: 52;
/* Exceptions */
--z-force: 99;
}
/* ✅ Non-Tailwind */
.modal {
z-index: var(--z-modal);
isolation: isolate;
}
.modal .combobox {
z-index: var(--z-modal-input);
}
Rules
✅ Required
- Always use a token from the layer table — either as a Tailwind class or CSS custom property
- Components that act as containers (Modal, Drawer, Panel) must create an isolated stacking context — see Stacking Context below
- Popovers, Context Menus and Tooltips should be rendered into a portal (e.g.
document.body) to avoid being clipped by parent stacking contexts oroverflow: hidden - When using
z-force/--z-force: add a comment with justification and ticket number directly in the code
<!-- ✅ Correct (Tailwind) -->
<div class="z-modal isolate">
<div class="z-modal-input"> <!-- Combobox inside a Modal -->
...
</div>
</div>
<!-- ✅ Correct with justification -->
<div class="z-force"> <!-- TODO: TICKET-123 – Workaround until legacy widget is replaced -->
/* ✅ Correct (Non-Tailwind) */
.modal {
z-index: var(--z-modal);
isolation: isolate;
}
❌ Forbidden
<!-- Arbitrary values (Tailwind) -->
<div class="z-[400]">
<div class="z-[9999]">
/* Magic numbers */
.my-element { z-index: 9999; }
/* z-index without position or display — has no effect and causes confusion */
.my-element { z-index: 30; } /* missing: position: relative/absolute/fixed/sticky */
Stacking Context
What is a Stacking Context?
A stacking context is an isolated layer in the browser's rendering model. Z-indexes inside a stacking context only apply relative to their siblings within that same context — not to the rest of the page. This means an element with z-index: 9999 inside a stacking context will never appear above an element outside of it that has a lower z-index.
This is the most common reason why a z-index seems to "not work".
How to create a Stacking Context
Any container that acts as a layer root (Modal, Panel, Drawer) must explicitly create a stacking context using isolation: isolate:
<!-- Tailwind -->
<div class="z-modal isolate"> ... </div>
/* Non-Tailwind */
.modal {
z-index: var(--z-modal);
isolation: isolate;
}
This ensures that all child elements — regardless of their z-index — can never accidentally overlap elements outside their container.
Important: Popovers must not be rendered inside a container with
isolation: isolate— they would be trapped inside that stacking context and could not appear above other layers. Always render popovers, tooltips and context menus into a portal (e.g.document.body).
What else creates a Stacking Context?
Besides isolation: isolate the browser automatically creates a new stacking context for elements with certain CSS properties. This can cause unexpected stacking behavior if not accounted for:
| CSS Property | Creates new Stacking Context |
|---|---|
isolation: isolate |
Yes (intentional) |
transform |
Yes |
opacity < 1 |
Yes |
filter |
Yes |
will-change |
Yes |
If a z-index seems to not work, check whether any parent element has one of these properties set unintentionally.
Choosing the right token inside a Container
Always use the sub-layer tokens that match the surrounding context:
<!-- Standalone Combobox on the page -->
<div class="relative z-base-input">
...
</div>
<!-- Combobox inside a Sticky Navbar -->
<div class="sticky z-sticky isolate"> <!-- Navbar -->
<div class="relative z-sticky-input"> <!-- Combobox -->
...
</div>
</div>
<!-- Burger Menu -->
<div class="fixed z-panel-backdrop"> <!-- Backdrop -->
...
</div>
<div class="fixed z-panel isolate"> <!-- Panel -->
<div class="relative z-panel-input"> <!-- Combobox inside Burger Menu -->
...
</div>
</div>
<!-- Combobox inside a Modal -->
<div class="fixed z-modal-backdrop"> <!-- Backdrop -->
...
</div>
<div class="fixed z-modal isolate"> <!-- Modal -->
<div class="relative z-modal-input"> <!-- Combobox -->
...
</div>
</div>
<!-- Context Menu (rendered into a portal, e.g. document.body) -->
<div class="fixed z-popover"> <!-- Context Menu -->
<ul>...</ul>
</div>
<!-- Tooltip on a Toast (rendered into a portal) -->
<div class="fixed z-popover"> <!-- Tooltip floats above the toast -->
...
</div>
If additional nested contexts are needed (e.g. a Modal triggered from inside a Panel), please discuss with the team before adding new tokens on your own.
Quick Reference: Which Layer do I need?
| Element | Layer / Token |
|---|---|
| Tooltip rendered inline (no portal) | z-base-raised |
Tooltip rendered into body (portal) |
z-popover |
| Combobox / Select dropdown in a form | z-base-input |
| Dropdown menu inside a navbar | z-sticky-input |
Dropdown menu rendered into body (portal) |
z-popover |
| Context menu (right-click) | z-popover |
| Toast / Snackbar | z-notification |
| Confirmation dialog | z-modal |
| Off-canvas navigation | z-panel |
| Drag preview / drag ghost | z-popover |
Debugging
Useful for debugging: Stacking Context Inspector (Chrome Extension)
Questions / Changes
New use cases that don't fit into the existing tokens → open an issue or post in the frontend channel. Tokens are not to be added or modified independently.