Design System · 8 of 25
Elevation
Depth, shadows, and z-index strategy.
Elevation philosophy
Depth should feel intentional, not decorative.
Elevation is how the interface says "this stands above that." Without a system, shadows become arbitrary — different values in different components, impossible to them, and contradictory in dark mode. A scale fixes this: every component references the same tokens, and switching themes adjusts the whole set at once.
Rules
Use a consistent elevation scale — never arbitrary pixel values.
Each level should have a documented purpose.
Shadows must adapt to both light and dark modes.
z-index should reference the same scale.
Avoid more than 4 elevation levels.
Flat surface with no shadow: the baseline for the scale.
Subtle shadow: hovers and small raised surfaces.
Card-level shadow: the most common raised surface.
Overlay shadow: modals, drawers and popovers.
Transient shadow: toasts and snackbars, the highest level.
The elevation scale
A 5-level scale with documented purposes.
The scale should be documented from level-0 (flat) up to the maximum level your system uses. Beyond that, components should not go. Here is a common starting point:
Examples
Hover button: box-shadow: var(--shadow-sm); z-index: var(--z-index-1)
Card: box-shadow: var(--shadow-md); z-index: var(--z-index-2)
Modal: box-shadow: var(--shadow-lg); z-index: var(--z-index-3)
Rules
Define all 5 levels in CSS custom properties.
z-index follows the same numeric scale.
Dark mode adjusts the shadow color and spread, not the level.
Never skip levels — if you need more depth, reconsider the hierarchy.
Document each level's purpose before shipping.
:root {
--shadow-none: none;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--z-index-0: auto;
--z-index-1: 1;
--z-index-2: 2;
--z-index-3: 3;
--z-index-4: 4;
}z-index strategy
z-index should use the same scale as shadows.
z-index and shadows are two sides of the same coin. When you raise an element's z-index, you should also give it a proportional shadow. This makes the visual relationship between elements explicit and consistent.
Examples
Nav: z-index: var(--z-index-1)
Dropdown: z-index: var(--z-index-2)
Modal: z-index: var(--z-index-3)
Drawer: z-index: var(--z-index-3)
Toast: z-index: var(--z-index-4) — transient, highest
Rules
Never use raw numbers like 999 or 9999.
z-index must pair with a proportional shadow.
Drawer and modal both use the same level — drawer can be lower if it has a backdrop.
Transient UI (toasts, snackbars) gets the highest level.
Test z-index in both light and dark modes.
Elevation in dark mode
Shadows must adapt, not invert.
Inverting shadows (making them larger or darker) in dark mode usually looks wrong — they can appear as floating blobs or lose contrast. Instead, adjust the shadow color, spread, and opacity while keeping the same level number. The scale numbers stay; only the CSS values shift.
Examples
level-1 light: var(--shadow-sm)
level-1 dark: 0 1px 2px 0 rgba(0, 0, 0, 0.3)
level-2 light: var(--shadow-md)
level-2 dark: 0 4px 6px -1px rgba(0, 0, 0, 0.4)
level-3 light: var(--shadow-lg)
level-3 dark: 0 10px 15px -3px rgba(0, 0, 0, 0.45)
Rules
Keep the level number the same across themes.
Adjust color, spread, and opacity — not the level.
Test shadow readability in both themes.
Avoid inverted shadows (never make them larger/darker in dark mode).
Use color-mix in CSS for smooth transitions between themes.
.shape {
box-shadow: var(--shadow-md);
transition: box-shadow 0.2s ease;
}
[data-theme="dark"] .shape {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
}Shadows adapt to the dark surface: same level, adjusted contrast.
Practical guidelines
Tips for applying elevation consistently.
Start with level-0 as the baseline. Only raise elevation when the visual hierarchy requires it. Use the same shadow scale in CSS and in the design system documentation. Always test in both light and dark modes. Document each level's purpose so teammates know when to reach for which level.
Do
Use level-1 for hovers, level-2 for cards, level-3 for modals. Test in dark mode.
Don't
Don't use raw z-index numbers. Don't skip levels. Don't forget dark mode adjustments.
Rules
Start with level-0 as the baseline.
Only raise elevation when the hierarchy requires it.
Use the same scale in CSS, documentation, and components.
Always test in both light and dark modes.
Document each level's purpose before shipping.
Never exceed the documented maximum without a codemod and release note.
// Card component using elevation tokens
export function Card({ children }: { children: React.ReactNode }) {
return (
<div
className="card"
style={{
boxShadow: 'var(--shadow-md)',
zIndex: 'var(--z-index-2)',
background: 'var(--color-paper)',
transition: 'box-shadow 0.2s ease',
}}
>
{children}
</div>
);
}Checklist
Verify your elevation implementation.
Scale
All 5 levels documented in CSS custom properties. z-index follows the same numeric scale.
Dark mode
Shadows adapt to dark mode without arbitrary changes. Level numbers stay the same.
z-index
z-index references the same scale as shadows. No raw numbers.
Components
Every component uses elevation tokens, not hard-coded shadows.