CSS Z-Index Explained: Stop the Stacking Chaos & Manage Layers Like a Pro
Source: Dev.to
If you’ve ever tried to put a dropdown menu over a header, or fought with a modal that stubbornly appears behind everything else, you’ve run into the wild world of CSS z-index. It seems simple—just pick a bigger number to be on top, right?—until it suddenly and infuriatingly stops working. This guide breaks down what z-index really is, why it behaves the way it does, and shares professional strategies to manage it with confidence, even in massive projects.
What is Z‑Index, Really?
Technically, z-index controls the stacking order of positioned elements along the z‑axis (the imaginary line coming out of your screen toward you). A higher z-index value means the element is closer to the user.
Important catch: z-index only works on elements that are positioned—i.e., have position: absolute, relative, fixed, or sticky. It also works on flex items. Applying z-index to a statically positioned element (the default) does nothing.
The #1 Thing Everyone Gets Wrong: Stacking Contexts
Creating a new stacking context changes the rules. When an element creates a stacking context, the z-index values of its children are contained; they can only compete with each other, not with elements outside the parent.
How a New Stacking Context Is Created
A stacking context is formed not only by setting a non‑auto z-index on a positioned element, but also by several other CSS properties, often unexpectedly:
opacityless than 1- Any
transform(e.g.,translate,scale,rotate) filter,mix-blend-mode,isolation- Certain flex and grid configurations
This explains the classic head‑scratcher: “Why is my child with z-index: 9999 still behind this other element with z-index: 5?” The answer is usually that the child’s parent created a stacking context with a lower effective stacking order.
Real‑World Example: The Trapped Tooltip
(Insert your own code or description of a tooltip that is hidden because its parent forms a stacking context.)
Professional Practices for Managing Z‑Index
The Local vs. Global Rule
-
Local: Layering within a single component (e.g., a button’s icon over its background, a card’s shadow over the next card). Use small numbers like
z-index: 1and contain them in a new stacking context on the component’s root:.component { position: relative; z-index: 0; /* creates a local stacking context */ } -
Global: Page‑wide layering of major UI pieces that need to interact across components (e.g., header, modals, dropdowns, sidebars, notifications). Manage these values from a single, central location.
Centralize Your Global Z‑Index Scale
Define all global layers in one file using CSS variables (or a preprocessor).
:root {
--z-index-header: 100;
--z-index-dropdown: 200;
--z-index-modal-overlay: 300;
--z-index-modal: 400;
--z-index-notification: 500;
--z-index-tooltip: 600;
}
Starting at 100 gives you a psychological and practical buffer. Values below 100 can be used for local stacking without worrying about clashes.
Use a Logical, Maintainable System
Incrementing by 100 (or another comfortable step) provides ample room (99 slots!) to insert new global layers without refactoring everything. For example, a new “sidebar” layer between the dropdown and modal could be 250.
Key takeaway: The absolute number is irrelevant; it’s the relative order that matters. z-index: 2 will always be above z-index: 1 and below z-index: 3, regardless of how large the numbers are.
Real‑World Use Cases & Code Snippets
Fixed Header with Shadow
.site-header {
position: fixed;
top: 0;
width: 100%;
z-index: var(--z-index-header); /* 100 */
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
Modal Dialog
/* Overlay – sits just below the modal */
.modal-overlay {
position: fixed;
inset: 0; /* shorthand for top/right/bottom/left: 0 */
background: rgba(0,0,0,0.5);
z-index: var(--z-index-modal-overlay); /* 300 */
}
/* Modal content – must be above the overlay */
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: var(--z-index-modal); /* 400 */
}
Dropdown Menu (Local Context)
.nav-item {
position: relative; /* positioning reference */
z-index: 0; /* creates a new stacking context */
}
.dropdown {
position: absolute;
top: 100%;
z-index: 1; /* competes only inside .nav-item */
}
FAQs: Your Burning Z‑Index Questions, Answered
Q: Can I use negative z-index values?
A: Yes, but only on positioned elements. Negative values place the element behind its stacking context’s background and may cause it to become inaccessible to pointer events.
Q: How many z-index layers do I really need?
A: As many as your UI requires, but keep the scale logical and documented. Overusing arbitrary numbers leads to maintenance headaches.
Q: What’s the maximum or minimum value for z-index?
A: The specification defines z-index as an integer. In practice, browsers support values up to at least 2,147,483,647 (32‑bit signed integer). Using extremely large numbers is discouraged; rely on relative ordering instead.