Europe/Kyiv
Posts

The z-index War — and How to End It

April 1, 2025
Familiar situation? You set z-index: 999, your colleague sets 1000, and before long you're writing z-index: 99999 just to show a tooltip. 😅 This is the z-index war — and there are no winners. Using huge numbers is a code smell. But the worst part is that it often doesn't even work. Why? Because of the Stacking Context. Properties like opacity, transform, or position create new "mini-worlds" where z-index counting starts over. Your z-index: 9999 in one world can easily end up beneath z-index: 1 in another.
/* Parent creates a new stacking context */
.parent {
  transform: translateZ(0); /* triggers new context */
  position: relative;
}

/* This z-index is now scoped to .parent's world */
.child {
  z-index: 9999; /* won't escape .parent */
}
Common properties that create a new stacking context: opacity (less than 1), transform, filter, will-change, isolation: isolate. Instead of chaos, create a z-index scale for your project. Define it once in your :root or Tailwind config:
/* In your :root */
:root {
  --z-dropdown: 10;
  --z-header: 20;
  --z-tooltip: 30;
  --z-modal: 50;
  --z-notification: 100;
}
Or in a Tailwind config:
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      zIndex: {
        'dropdown': '10',
        'header': '20',
        'tooltip': '30',
        'modal': '50',
        'notification': '100',
      },
    },
  },
};
Now instead of magic numbers you write:
/* ❌ Before */
.modal {
  z-index: 99999;
}

/* ✅ After */
.modal {
  z-index: var(--z-modal);
}
A named scale makes layers predictable and code clean and maintainable. You always know where each element lives in the stack — no more guessing, no more escalation wars. The next time you reach for z-index: 9999, pause and ask: do I have a stacking context issue, or do I just need a scale? How does your team handle z-index — with a scale or by intuition? 😅