Component-Scoped z-index Architecture
How to design a z-index system where components manage their own internal layers without leaking into the global stacking order.
Detailed Explanation
Component-Scoped z-index
Modern web applications are built from composable components. Each component should manage its own internal z-index without affecting the global stacking order. This is achieved through a two-tier z-index architecture.
Two-Tier z-index System
/* Tier 1: Global z-index scale (application-level) */
:root {
--z-dropdown: 1000;
--z-sticky: 1020;
--z-overlay: 1040;
--z-modal: 1050;
--z-toast: 1080;
--z-tooltip: 1090;
}
/* Tier 2: Local z-index values (component-level) */
/* These are small numbers: 1, 2, 3 */
/* They only work within a component's stacking context */
Implementation
/* Card component with internal layering */
.card {
position: relative;
isolation: isolate; /* creates scoped stacking context */
}
.card__image { z-index: 1; }
.card__gradient-overlay { z-index: 2; }
.card__content { z-index: 3; }
.card__badge { z-index: 4; }
/* Header component with internal layering */
.header {
position: sticky;
top: 0;
z-index: var(--z-sticky); /* global tier */
isolation: isolate;
}
.header__background { z-index: 1; } /* local tier */
.header__nav { z-index: 2; }
.header__dropdown { z-index: 3; }
Rules for Component-Scoped z-index
Global values only on component roots: Only the outermost element of a component uses the global z-index scale.
Local values for internals: Internal elements use small, sequential z-index values (1, 2, 3).
Always use isolation: Add
isolation: isolateto component roots that contain z-indexed children.Never reference global values inside components: A card's badge should use
z-index: 4, notz-index: 1090.Document the global scale: Share the global z-index scale as a team reference (CSS variables, Sass map, or TypeScript object).
Benefits
- Components are self-contained and portable
- No z-index conflicts between components
- Easy to reason about layering within a component
- Global scale stays clean with only a few well-defined levels
Use Case
When building a large-scale application or design system where multiple teams contribute components and you need to prevent z-index conflicts across component boundaries.