Skillversitas

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">&times;</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):

  1. Background & border of the element itself
  2. Negative z-index (z-index: -1)
  3. Block elements in the normal flow
  4. Float elements
  5. Inline elements in the normal flow
  6. Z-index: 0 / auto
  7. 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:

  1. Z-index only works on elements with a position other than static
  2. Stacking context isolates z-index — a child cannot "break out" of its parent's context
  3. Properties that create a stacking context: z-index (other than auto), opacity < 1, transform, filter, isolation: isolate
  4. Use a consistent z-index scale — dropdown: 1000, sticky: 1020, fixed: 1030, modal: 1040-1050, toast: 1060
  5. 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.