What is Z-Index in CSS?
Z-index is one of the most powerful yet frequently misunderstood CSS properties. It controls the vertical stacking order of elements, determining which elements appear in front of and behind others when they overlap.
Think of your webpage as existing in three dimensions: x (horizontal), y (vertical), and z (depth). While x and y positions control where elements sit on the page, z-index controls which elements are closer to the viewer through the screen.
Understanding z-index is essential for creating complex layouts with modals, dropdowns, sticky headers, notification toasts, and visually rich designs. Without it, overlapping elements would stack in source order alone, limiting design possibilities.
In modern web development, z-index plays a critical role in user interface patterns. Modal dialogs rely on high z-index values to appear above all other content. Dropdown navigation menus need proper stacking to float above page content. Hero section overlays position text above background images. Tooltips and hover cards use z-index to appear contextually above their trigger elements. Even sticky headers that remain visible during scrolling depend on z-index to stay above the document content.
.element {
z-index: auto; /* Default - same stacking level as parent */
z-index: 0; /* Explicit layer 0 */
z-index: 10; /* Positive value - above default */
z-index: -5; /* Negative value - below default */
}How Z-Index Works: The Fundamentals
The Critical Position Requirement
Z-index only works on positioned elements. This is the most common misunderstanding about z-index. An element must have a position value other than static for z-index to have any effect.
The position values that activate z-index:
position: relativeposition: absoluteposition: fixedposition: sticky
Default Stacking Behavior
Without z-index, elements stack based on their order in the HTML. Later elements appear on top of earlier ones (source order). Positioned elements automatically stack above non-positioned elements, regardless of where they appear in the markup.
For example, if you have three div elements in source order--first, second, and third--without any z-index values, the third element will appear on top of the second, which appears on top of the first. This behavior can be surprising when layout changes cause previously non-overlapping elements to stack in unexpected ways.
When elements have the same z-index value, the one that appears later in the HTML document takes precedence. This "painters algorithm" approach means the document order determines stacking when no explicit z-index is specified.
Understanding Stacking Contexts
What Creates a Stacking Context?
A stacking context is like an isolated stacking environment. When a new stacking context is created, all descendant elements stack relative to each other, but their z-index values are only meaningful within this boundary. Think of it as a "mini stacking system" contained within a parent element.
An element with z-index: 100 inside a parent with z-index: 1 may not appear above an element with z-index: 2 outside the parent. The child's z-index is relative to the parent's context, not the entire page. This isolation effect is the key to understanding complex z-index behavior.
Properties that create new stacking contexts:
- Any positioned element with
z-indexother thanauto - Any element with
opacityless than 1 (e.g.,opacity: 0.5) - Any element with
transformother thannone - Any element with
filterapplied - Any element with
clip-pathapplied - Child elements of flex containers with
z-indexother thanauto - Child elements of grid containers with
z-indexother thanauto - Any element with
isolation: isolate
The MDN Web Docs provide authoritative documentation on these stacking context rules, while Ahmad Shadeed's visual guide offers excellent interactive examples demonstrating how stacking contexts work in practice.
Default Stacking Order Within a Context
Within a single stacking context, elements stack from back to front in this specific order:
| Layer | Element Type | Description |
|---|---|---|
| 1 | Background and borders | Of the stacking context's root element |
| 2 | Negative z-index | Child elements with negative values (lowest first) |
| 3 | Non-positioned blocks | Block-level elements in normal flow |
| 4 | Non-positioned floats | Floated elements without positioning |
| 5 | Inline elements | Text, images, inline-block elements |
| 6 | Positioned (auto) | Positioned elements with z-index: auto |
| 7 | Positive z-index | Positioned elements with positive values (lowest first) |
This layered approach means that an element with z-index: -1 will appear behind the parent's background but in front of the parent's content. Elements with negative z-index values stack in ascending order--z-index: -2 appears behind z-index: -1.
Common Z-Index Problems and Solutions
Problem 1: Z-Index Not Working
Symptoms: You've set z-index but elements still don't stack as expected.
Solutions:
- Check if the element has a
positionvalue set (notstatic) - Check if a parent element has created a stacking context via opacity, transform, etc.
- Use browser DevTools to inspect the stacking context hierarchy
Problem 2: Element "Trapped" in Parent Context
Symptoms: A dropdown menu inside a header appears behind page content even with high z-index.
Cause: The parent created a stacking context, limiting the child's effective z-index. For example, if the header has opacity: 0.9 or transform: translateX(0), it creates a new context that bounds the dropdown's z-index.
Solutions:
- Apply z-index to the parent container instead of the child
- Remove the parent element's stacking context property (if possible)
- Move the child outside the stacking context in the HTML structure
Problem 3: Negative Z-Index Hiding Elements
Symptoms: Elements with negative z-index disappear behind the parent's background.
Explanation: Elements with negative z-index are stacked behind the parent's content but still in front of the parent's background and border. If you need an element to appear behind everything, you may need to restructure the HTML or adjust the parent's stacking context.
Debugging Workflow
When z-index issues arise, follow this systematic approach:
- Verify positioning - Open DevTools and confirm the element has a non-static position value
- Identify parent contexts - Check ancestors for opacity, transform, filter, or clip-path that might create stacking contexts
- Calculate effective z-index - Use DevTools to see the element's position within its local stacking context
- Adjust the appropriate level - Either modify the local element or adjust the parent context to achieve the desired stacking
For teams working on professional web development projects, establishing clear z-index conventions from the start prevents these common issues from affecting production code.
Practical Z-Index Patterns
Pattern 1: Modal Overlays
Full-screen modals must appear above all other content:
.modal-overlay {
position: fixed;
z-index: 1000;
inset: 0;
}
.modal-content {
position: relative;
z-index: 1001;
}
Pattern 2: Sticky Header Navigation
Headers that stay above page content while scrolling:
.header {
position: sticky;
z-index: 50;
top: 0;
}
Pattern 3: Dropdown Menus
Nested menus that appear above sibling content:
.dropdown {
position: relative;
z-index: 10;
}
.dropdown-menu {
position: absolute;
z-index: 20;
}
Pattern 4: Card Overlays
Cards with content floating above background images:
.card {
position: relative;
}
.card-content {
position: relative;
z-index: 1;
}
.card-background {
position: absolute;
z-index: 0;
}
Pattern 5: Notification Toasts
Toast notifications that appear above most content:
.toast {
position: fixed;
z-index: 500;
bottom: 1rem;
right: 1rem;
}
Pattern 6: Tooltips
Small contextual popups that appear above surrounding content:
.tooltip {
position: absolute;
z-index: 100;
}
Z-Index Best Practices
Organize Your Z-Index Scale
Avoid arbitrary numbers. Use a systematic approach with CSS custom properties:
:root {
--z-dropdown: 100;
--z-sticky: 200;
--z-modal-backdrop: 300;
--z-modal: 400;
--z-toast: 500;
--z-loading: 600;
}
Avoid Magic Numbers
Z-index values like 9999 or 99999 indicate design problems:
- They suggest uncertainty about the z-index hierarchy
- They make maintenance difficult when adding new components
- They can conflict with third-party code libraries
- A well-organized scale makes your intent clear to other developers
Use Semantic Naming
Name z-index values by purpose, not by level:
--z-dropdowninstead ofz-100--z-modalinstead ofz-999- Makes code self-documenting and easier to understand
- New team members can immediately understand component layering intent
Test in Context
Test z-index behavior with:
- Parent containers that have transforms applied
- Opacity changes for hover states or animations
- Different viewport sizes on various devices
- Nested components with multiple z-index levels
- Browser DevTools stacking context visualization features
Document Your Scale
For larger projects, create a documentation comment or style guide that explains your z-index scale:
/*
Z-Index Scale
============
0-99: Base elements (cards, content blocks)
100-199: Tooltips, popovers, dropdowns
200-299: Sticky headers, navigation
300-399: Modal backdrops
400-499: Modal content
500+: Notifications, loading overlays
*/
Following these CSS best practices ensures your layering code remains maintainable as projects grow in complexity.
| Scenario | Z-Index Needed | Position Required |
|---|---|---|
| Dropdown menu | Yes (e.g., 10-20) | Yes (relative or absolute) |
| Sticky header | Yes (e.g., 50) | Yes (sticky or fixed) |
| Modal overlay | Yes (e.g., 400-500) | Yes (fixed) |
| Tooltip | Yes (e.g., 50-100) | Yes |
| Notification toast | Yes (e.g., 500+) | Yes (fixed) |
| Hover effects only | Sometimes | Usually |
| Background element | Negative | Yes |