CSS Z-Index & Stacking Context
Z-index is one of the most confusing concepts in CSS, yet it is essential for building complex UI components like modals, tooltips, dropdowns, and overlays. Z-index controls the stacking order — the order in which elements stack along the Z axis (depth).
To understand z-index, you must understand the stacking context — the CSS rules that determine when z-index works and when it doesn't. An element can only compete on z-index with its siblings inside the same stacking context.
Basic principles:
- Z-index only works on elements with a position other than static (i.e., relative, absolute, fixed, sticky)
- The higher the z-index number, the more "on top" the element is
- Stacking contexts can be created by position, opacity < 1, transform, filter, and others
Code Example
Comprehensive Z-Index & Stacking
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Z-Index & Stacking Context</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, sans-serif;
padding: 30px;
background: #f0f2f5;
color: #334155;
}
h2 {
margin-bottom: 15px;
color: #1a1a2e;
}
.section {
margin-bottom: 50px;
}
.label {
font-size: 12px;
color: #64748b;
font-family: monospace;
background: #e2e8f0;
display: inline-block;
padding: 3px 8px;
border-radius: 4px;
margin-bottom: 15px;
}
/* ===== BASIC Z-INDEX DEMO ===== */
.z-demo {
position: relative;
height: 350px;
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.z-box {
position: absolute;
width: 180px;
height: 120px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 14px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.z-box-1 {
top: 30px;
left: 30px;
background: #667eea;
z-index: 1;
}
.z-box-2 {
top: 70px;
left: 120px;
background: #f5576c;
z-index: 3;
}
.z-box-3 {
top: 130px;
left: 60px;
background: #43e97b;
z-index: 2;
}
.z-box-4 {
top: 50px;
left: 250px;
background: #fa709a;
z-index: 4;
}
.z-box-5 {
top: 160px;
left: 200px;
background: #a18cd1;
z-index: 0;
}
/* ===== STACKING CONTEXT DEMO ===== */
.stacking-demo {
display: flex;
gap: 30px;
margin-top: 15px;
}
.stacking-parent {
position: relative;
width: 300px;
height: 250px;
border: 3px solid #e2e8f0;
border-radius: 12px;
padding: 15px;
background: white;
}
.stacking-parent h3 {
font-size: 14px;
margin-bottom: 10px;
color: #64748b;
}
.stacking-child {
position: absolute;
padding: 12px 18px;
border-radius: 8px;
color: white;
font-weight: 600;
font-size: 13px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
}
/* Parent A — No stacking context creator */
.parent-a .child-a1 {
top: 50px;
left: 20px;
background: #667eea;
z-index: 10;
}
.parent-a .child-a2 {
top: 80px;
left: 100px;
background: #f5576c;
z-index: 20;
}
/* Parent B — Creates stacking context via opacity */
.parent-b {
opacity: 0.99;
}
.parent-b .child-b1 {
top: 50px;
left: 20px;
background: #43e97b;
z-index: 100; /* High but limited to Parent B */
}
.parent-b .child-b2 {
top: 80px;
left: 100px;
background: #fa709a;
z-index: 50;
}
/* ===== OVERLAY EXAMPLE ===== */
.overlay-demo {
position: relative;
height: 300px;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.overlay-bg {
position: absolute;
inset: 0;
background: #1e293b;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
}
.overlay-content {
position: relative;
z-index: 2;
background: white;
padding: 30px;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
text-align: center;
max-width: 400px;
}
.overlay-mask {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 1;
}
/* ===== TOOLTIP ===== */
.tooltip-demo {
display: flex;
gap: 40px;
padding: 30px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
justify-content: center;
}
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip-trigger {
padding: 10px 20px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%);
background: #1e293b;
color: white;
padding: 8px 14px;
border-radius: 8px;
font-size: 13px;
white-space: nowrap;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
z-index: 1000;
}
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #1e293b;
}
.tooltip-wrapper:hover .tooltip {
opacity: 1;
visibility: visible;
}
.tooltip-right {
left: auto;
right: -10px;
transform: none;
}
.tooltip-right::after {
left: auto;
right: 10px;
transform: none;
}
/* ===== MODAL ===== */
.modal-demo {
position: relative;
height: 350px;
background: #f8fafc;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.modal-demo-content {
text-align: center;
}
.modal-demo-content p {
color: #64748b;
margin-bottom: 15px;
}
.btn-modal {
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
}
.modal-overlay {
position: absolute;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1040;
backdrop-filter: blur(4px);
}
.modal-box {
background: white;
border-radius: 16px;
padding: 30px;
max-width: 400px;
width: 90%;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
position: relative;
}
.modal-box h3 {
font-size: 20px;
margin-bottom: 10px;
}
.modal-box p {
color: #64748b;
margin-bottom: 20px;
line-height: 1.5;
}
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.modal-actions button {
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
border: none;
}
.modal-cancel {
background: #f1f5f9;
color: #475569;
}
.modal-confirm {
background: #667eea;
color: white;
}
.modal-close {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 20px;
color: #94a3b8;
cursor: pointer;
}
/* ===== Z-INDEX SCALE ===== */
.scale-table {
width: 100%;
border-collapse: collapse;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.scale-table th,
.scale-table td {
padding: 12px 20px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.scale-table th {
background: #1e293b;
color: white;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.scale-table .z-visual {
display: flex;
align-items: center;
gap: 10px;
}
.scale-table .z-bar {
height: 24px;
border-radius: 4px;
display: flex;
align-items: center;
padding: 0 10px;
color: white;
font-size: 11px;
font-weight: bold;
}
</style>
</head>
<body>
<!-- Basic Z-Index -->
<div class="section">
<h2>Z-Index Basics</h2>
<span class="label">z-index: 0 | 1 | 2 | 3 | 4</span>
<p style="color: #64748b; margin-bottom: 15px;">
Z-index determines stacking order. Higher number = on top. Only works on elements with a <code>position</code> other than <code>static</code>.
</p>
<div class="z-demo">
<div class="z-box z-box-1">z-index: 1</div>
<div class="z-box z-box-3">z-index: 2</div>
<div class="z-box z-box-2">z-index: 3</div>
<div class="z-box z-box-5">z-index: 0</div>
<div class="z-box z-box-4">z-index: 4 (topmost)</div>
</div>
</div>
<!-- Stacking Context -->
<div class="section">
<h2>Stacking Context</h2>
<span class="label">opacity, transform, filter, z-index create a new stacking context</span>
<p style="color: #64748b; margin-bottom: 15px;">
Each stacking context is independent. A child's z-index only competes with siblings in the same context. Parent B has <code>opacity: 0.99</code>, which creates a new stacking context — even though child-b1 has z-index: 100, it stays below child-a2 (z-index: 20) because it is in a different context.
</p>
<div class="stacking-demo">
<div class="stacking-parent parent-a">
<h3>Parent A (no stacking context)</h3>
<div class="stacking-child child-a1">Child A1<br>z-index: 10</div>
<div class="stacking-child child-a2">Child A2<br>z-index: 20</div>
</div>
<div class="stacking-parent parent-b">
<h3>Parent B (opacity: 0.99 → stacking context)</h3>
<div class="stacking-child child-b1">Child B1<br>z-index: 100</div>
<div class="stacking-child child-b2">Child B2<br>z-index: 50</div>
</div>
</div>
</div>
<!-- Z-Index Scale -->
<div class="section">
<h2>Recommended Z-Index Scale</h2>
<table class="scale-table">
<thead>
<tr>
<th>Component</th>
<th>Value</th>
<th>Visual</th>
</tr>
</thead>
<tbody>
<tr>
<td>Base content</td>
<td>0</td>
<td><div class="z-visual"><div class="z-bar" style="width:5%;background:#94a3b8;">0</div></div></td>
</tr>
<tr>
<td>Raised content</td>
<td>100</td>
<td><div class="z-visual"><div class="z-bar" style="width:20%;background:#667eea;">100</div></div></td>
</tr>
<tr>
<td>Dropdown</td>
<td>1000</td>
<td><div class="z-visual"><div class="z-bar" style="width:40%;background:#43e97b;">1000</div></div></td>
</tr>
<tr>
<td>Sticky</td>
<td>1020</td>
<td><div class="z-visual"><div class="z-bar" style="width:55%;background:#4facfe;">1020</div></div></td>
</tr>
<tr>
<td>Fixed</td>
<td>1030</td>
<td><div class="z-visual"><div class="z-bar" style="width:65%;background:#f093fb;">1030</div></div></td>
</tr>
<tr>
<td>Modal backdrop</td>
<td>1040</td>
<td><div class="z-visual"><div class="z-bar" style="width:75%;background:#fa709a;">1040</div></div></td>
</tr>
<tr>
<td>Popover</td>
<td>1050</td>
<td><div class="z-visual"><div class="z-bar" style="width:85%;background:#f5576c;">1050</div></div></td>
</tr>
<tr>
<td>Toast</td>
<td>1060</td>
<td><div class="z-visual"><div class="z-bar" style="width:95%;background:#dc2626;">1060</div></div></td>
</tr>
</tbody>
</table>
</div>
<!-- Tooltip -->
<div class="section">
<h2>Tooltip</h2>
<span class="label">z-index: 1000</span>
<div class="tooltip-demo">
<div class="tooltip-wrapper">
<button class="tooltip-trigger">Hover Me</button>
<div class="tooltip">This is a tooltip!</div>
</div>
<div class="tooltip-wrapper">
<button class="tooltip-trigger">Right Tooltip</button>
<div class="tooltip tooltip-right">Tooltip on the right</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="section">
<h2>Modal / Dialog</h2>
<span class="label">z-index: 1040 (backdrop) + modal content</span>
<div class="modal-demo">
<div class="modal-demo-content">
<p>Click the button to open the modal</p>
<button class="btn-modal">Open Modal</button>
</div>
<div class="modal-overlay">
<div class="modal-box">
<button class="modal-close">×</button>
<h3>Confirm Deletion</h3>
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
<div class="modal-actions">
<button class="modal-cancel">Cancel</button>
<button class="modal-confirm">Delete</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Code Explanation
z-index Property
.element {
position: relative; /* or absolute, fixed, sticky */
z-index: 10;
}
Rules:
- Z-index only works on elements with a position other than static
- The default z-index is auto (the same as 0 in many cases)
- The higher the number, the more "on top"
Stacking Context
A stacking context is an isolation hierarchy for z-index. Some properties that create a new stacking context:
/* Properties that create a stacking context */
.parent {
position: relative;
z-index: 0; /* z-index other than auto */
opacity: 0.99; /* opacity < 1 */
transform: scale(1); /* any transform */
filter: blur(0); /* any filter */
isolation: isolate; /* explicit isolation */
}
Important: If a parent creates a stacking context, a child's z-index only competes within that context — it cannot "break out" into the parent's context.
Stacking Order Rules
Stacking order (from bottom to top):
- Background & border of the element itself
- Negative z-index (z-index: -1)
- Block elements in the normal flow
- Float elements
- Inline elements in the normal flow
- Z-index: 0 / auto
- Positive z-index (from small to large)
Common Z-Index Scale
A recommended, consistent scale:
/* Based on common needs */
:root {
--z-base: 0;
--z-raised: 100;
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-toast: 1070;
}
.dropdown { z-index: var(--z-dropdown); }
.navbar { z-index: var(--z-sticky); }
.modal { z-index: var(--z-modal); }
.toast { z-index: var(--z-toast); }
Everyday Use Cases
1. Sticky Header + Dropdown + Modal
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; }
.page-header {
position: sticky;
top: 0;
z-index: 1020;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30px;
height: 60px;
background: rgba(255,255,255,0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid #e2e8f0;
}
.page-header .brand {
font-weight: 800;
font-size: 20px;
}
.nav-item {
position: relative;
}
.nav-item > a {
text-decoration: none;
color: #475569;
padding: 10px 16px;
border-radius: 8px;
font-weight: 500;
}
.nav-item:hover > a {
background: #f1f5f9;
}
.dropdown-menu {
position: absolute;
top: calc(100% + 8px);
left: 0;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.12);
min-width: 180px;
padding: 6px;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all 0.3s;
z-index: 1000;
}
.nav-item:hover .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-menu a {
display: block;
padding: 10px 14px;
color: #475569;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
}
.dropdown-menu a:hover {
background: #f1f5f9;
color: #667eea;
}
.page-body {
padding: 30px;
}
.content-card {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.06);
margin-bottom: 20px;
}
.toast {
position: fixed;
bottom: 24px;
right: 24px;
background: #1e293b;
color: white;
padding: 14px 24px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
z-index: 1060;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
}
.toast .toast-icon {
font-size: 18px;
}
</style>
</head>
<body>
<header class="page-header">
<span class="brand">MyApp</span>
<nav style="display:flex; gap: 4px;">
<div class="nav-item">
<a href="#">Home</a>
</div>
<div class="nav-item">
<a href="#">Products ▾</a>
<div class="dropdown-menu">
<a href="#">Product A</a>
<a href="#">Product B</a>
<a href="#">Product C</a>
</div>
</div>
<div class="nav-item">
<a href="#">Contact</a>
</div>
</nav>
</header>
<main class="page-body">
<div class="content-card">
<h2>Stacking Demo</h2>
<p style="color:#64748b; margin-top:10px;">
Header: z-index 1020 (sticky) | Dropdown: z-index 1000 | Toast: z-index 1060
</p>
<p style="color:#64748b; margin-top:10px;">
Scroll the page to see the sticky header. Hover over "Products" to open the dropdown.
</p>
</div>
</main>
<div class="toast">
<span class="toast-icon">✓</span> Changes saved successfully!
</div>
</body>
</html>
Explanation: The sticky header (z-index: 1020), the dropdown beneath it (z-index: 1000), and the toast notification above everything else (z-index: 1060). Each level does not interfere with the others because they sit in different stacking contexts.
2. Floating Action Button (FAB)
<!DOCTYPE html>
<html lang="en">
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: #f0f2f5;
padding: 30px;
}
.content {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.06);
}
.fab {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background: #667eea;
color: white;
border: none;
border-radius: 50%;
font-size: 28px;
cursor: pointer;
box-shadow: 0 6px 20px rgba(102,126,234,0.4);
z-index: 1030;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
}
.fab:hover {
transform: scale(1.1) rotate(90deg);
box-shadow: 0 8px 25px rgba(102,126,234,0.5);
}
.fab-tooltip {
position: fixed;
bottom: 100px;
right: 30px;
background: #1e293b;
color: white;
padding: 8px 14px;
border-radius: 8px;
font-size: 13px;
z-index: 1030;
opacity: 0;
transition: opacity 0.3s;
}
.fab:hover ~ .fab-tooltip {
opacity: 1;
}
.tooltip-bottom {
bottom: auto;
top: 100px;
}
</style>
</head>
<body>
<div class="content">
<h2>Page with a FAB</h2>
<p style="color:#64748b; margin-top:10px;">Floating Action Button in the bottom-right corner with z-index: 1030 (fixed level).</p>
<p style="color:#64748b; margin-top:10px;">Hover the button to see the rotation and tooltip.</p>
</div>
<button class="fab">+</button>
<div class="fab-tooltip">Create New</div>
</body>
</html>
Conclusion
Z-Index & Stacking Context are fundamental concepts for complex UI components. Key points:
- Z-index only works on elements with a
positionother thanstatic - Stacking context isolates z-index — a child cannot "break out" of its parent's context
- Properties that create a stacking context:
z-index(other than auto),opacity < 1,transform,filter,isolation: isolate - Use a consistent z-index scale — dropdown: 1000, sticky: 1020, fixed: 1030, modal: 1040-1050, toast: 1060
- Avoid excessively large z-index values (e.g., 99999) — use a structured scale
If z-index is not behaving as expected, check whether a parent is creating a new stacking context.