Why z-index Fails

z-index only works on positioned elements (position: relative/absolute/fixed/sticky). On elements with position: static (the default), z-index is completely ignored.

/* ❌ z-index ignored — element is not positioned */
.element {
  z-index: 999;
  /* missing position! */
}

/* ✅ z-index works */
.element {
  position: relative;  /* or absolute, fixed, sticky */
  z-index: 999;
}

What Creates a Stacking Context

When an element creates a stacking context, its children's z-index values only compete with each other — they can't escape to "beat" elements outside the context, no matter how high the z-index number.

Things that create a stacking context:

Debugging

// Check DevTools: Elements panel → Computed tab → look for:
// - Any transform on parent elements
// - opacity < 1 on parent elements
// - will-change values

// In console, check parent element computed styles:
const el = document.querySelector('.problem-element');
let parent = el.parentElement;
while (parent) {
  const styles = window.getComputedStyle(parent);
  if (styles.transform !== 'none' || styles.opacity !== '1') {
    console.log('Stacking context creator:', parent, styles.transform, styles.opacity);
  }
  parent = parent.parentElement;
}

The Fix

// Option 1: Remove the stacking context from the parent
// (if you don't need the transform/opacity)
.parent {
  /* remove: transform: translateZ(0); */
  /* remove: opacity: 0.99; */
}

// Option 2: Move the element outside the stacking context
// Use JavaScript to append it to document.body for modals/tooltips

// Option 3: Use isolation: isolate deliberately
// to contain stacking contexts intentionally
.card { isolation: isolate; }

// Option 4: Portal pattern (React)
// Render modals/tooltips at the document root level