The classic z-index bug: you set something to z-index: 999, something else to 9999, then a third thing breaks and you add 99999. It works, nobody knows why, and six months later nobody touches it.
The fix is to stop using arbitrary numbers and define a named layer scale instead.
CSS custom properties
:root {
--z-index-max: 9999;
--z-index-min: -9999;
--z-index-overlays: 1900;
--z-index-modal: 1800;
--z-index-header: 1700;
--z-index-drawer: 1600;
--z-index-backdrop: 1500;
--z-index-fixed: 1400;
--z-index-sticky: 1300;
--z-index-dropdown: 1200;
--z-index-floating: 1100;
--z-index-footer: 1000;
--z-index-0: 0;
--z-index-1: 10;
--z-index-2: 20;
--z-index-3: 30;
--z-index-4: 40;
--z-index-5: 50;
}Now every component uses a name, not a number:
.modal { z-index: var(--z-index-modal); }
.header { z-index: var(--z-index-header); }
.tooltip { z-index: var(--z-index-tooltip); }When a modal needs to appear above a sticky header, you don’t guess — the answer is already in the scale.
Tailwind
Extend the theme in tailwind.config.ts:
export default {
theme: {
extend: {
zIndex: {
'max': '9999',
'min': '-9999',
'overlays': '1900',
'modal': '1800',
'header': '1700',
'drawer': '1600',
'backdrop': '1500',
'fixed': '1400',
'sticky': '1300',
'dropdown': '1200',
'floating': '1100',
'footer': '1000',
},
},
},
}Then use z-modal, z-header, z-tooltip as utility classes.
SCSS
If you’re on Sass, a map works well:
$z-index: (
'max': 9999,
'min': -9999,
'modal': 1800,
'header': 1700,
'drawer': 1600,
'backdrop': 1500,
'dropdown': 1200,
'floating': 1100,
'footer': 1000,
);
@function z($layer) {
@return map.get($z-index, $layer);
}.modal { z-index: z('modal'); }Why it’s worth doing
All stacking decisions live in one place. When a conflict comes up, you don’t grep through the codebase looking for who wrote z-index: 9999 — you open the scale and reason about it in a minute.
Code review gets easier too. A PR with z-index: var(--z-index-modal) is self-explanatory. A PR with z-index: 10000 opens a conversation about why, and that conversation will happen at 5pm on a Friday.
The gaps matter more than they look. 100 units between each layer means you can insert a new one anywhere without touching existing values. Need something between dropdown and floating? 1150 fits and nothing else changes.
The numeric scale (--z-index-1 through --z-index-5, stepping by 10) is for local stacking within a component — a badge over an avatar, an overlay over a card. Small adjustments that don’t need a name. --z-index-min covers the other end: background decorations and pseudo-elements that need to sit behind everything else.