Elevation
Create depth with light and dark shadow tokens, layered surfaces, and state-aware elevation.
Overview
Elevation describes the relationship between surfaces. In CSS, that relationship is usually rendered with box-shadow, but the design decision is broader than the property: a surface can be grounded on the canvas, raised as a reusable card, detached as an overlay, or placed above the workflow as a modal.
Master CSS maps elevation to the shadow namespace. Use shadow:* tokens for repeated product surfaces instead of copying raw multi-part shadow values into markup. The preset scale is mode-aware, so the same class resolves to a light shadow recipe in light mode and a stronger dark-mode recipe in dark mode.
Small controls, table rows, and subtle raised states.
Cards, reusable panels, and quiet product surfaces.
Hover lift, floating toolbars, and command surfaces.
Dropdowns, popovers, toasts, and menus.
Drawers, dialogs, and focused panels.
Modals and spotlight surfaces above a dimmed page.
Choose an elevation token
Start with the surface relationship, then choose the lowest token that makes that relationship clear. More shadow does not create more hierarchy when every surface competes for depth.
| Token | Class | Role |
|---|---|---|
--shadow-xs | shadow:xs | Quiet separation, Small controls, table rows, and subtle raised states. |
--shadow-sm | shadow:sm | Standard surface, Cards, reusable panels, and quiet product surfaces. |
--shadow-md | shadow:md | Temporary lift, Hover lift, floating toolbars, and command surfaces. |
--shadow-lg | shadow:lg | Detached overlay, Dropdowns, popovers, toasts, and menus. |
--shadow-xl | shadow:xl | Workflow interruption, Drawers, dialogs, and focused panels. |
--shadow-2xl | shadow:2xl | Blocking layer, Modals and spotlight surfaces above a dimmed page. |
The role of each token should stay stable across modes. Light and dark recipes can use different opacity, offset, and blur values, but shadow:sm should still mean a standard raised surface in both modes.
Pair elevation with color roles
Elevation needs a visible ground. Pair shadows with surface:*, text:*, and radius tokens so depth reads correctly in light and dark themes.
Cards use a large shadow on a raised surface.
Cards use a large shadow on a raised surface.
<article class="surface:raised r:lg shadow:lg"> <h2 class="text:strong">Raised surface</h2> <p class="text:muted">Cards need a clear surface before they need a large shadow.</p></article>Use surface roles for the ground and shadows for separation. A strong shadow on every panel makes the page noisy, even when the surfaces use different color roles.
Use shadow tokens
Use shadow:<token> for common elevation levels. The utility resolves values from shadow-* tokens and emits a stable box-shadow: var(--shadow-*) declaration; the active mode changes the variable value.
<article class="surface:raised r:lg shadow:sm"> ...</article><div class="surface:overlay r:lg shadow:lg"> ...</div>Generated CSS
@layer theme { @media (prefers-color-scheme:light) { :root { --shadow-sm: 0 0 0 1px oklch(0% 0 none / .04), 0 1px 2px -1px oklch(0% 0 none / .06), 0 3px 6px -2px oklch(0% 0 none / .08); --shadow-lg: 0 0 0 1px oklch(0% 0 none / .05), 0 4px 8px -4px oklch(0% 0 none / .08), 0 16px 32px -8px oklch(0% 0 none / .12) } } @media (prefers-color-scheme:dark) { :root { --shadow-sm: 0 0 0 1px oklch(100% 0 none / .05), 0 1px 2px -1px oklch(0% 0 none / .34), 0 3px 6px -2px oklch(0% 0 none / .26); --shadow-lg: 0 0 0 1px oklch(100% 0 none / .06), 0 4px 8px -4px oklch(0% 0 none / .42), 0 16px 32px -8px oklch(0% 0 none / .34) } }}@layer utilities { .shadow\:lg { box-shadow: var(--shadow-lg) } .shadow\:sm { box-shadow: var(--shadow-sm) }}Use shadow:* in product markup. Use the explicit box-shadow:* form when documenting native property mapping or when surrounding code benefits from the native property name.
Namespace for elevation
The shadow variable namespace is shared by the elevation shorthand and the native shadow property.
| Group | Utility keys | Description |
|---|---|---|
| Shadow | shadow, box-shadow | Use shadow tokens for product elevation and the native property name when surrounding code is clearer that way. |
This lets elevation utilities use sm, lg, or project token names without repeating the shadow- prefix.
Change elevation by state
State elevation should communicate interaction, not decoration. A hover shadow can make a card feel liftable; a focus shadow should not replace an accessible focus indicator.
<article class="shadow:sm shadow:md:hover shadow:none@print"> ...</article>Generated CSS
@layer theme { @media (prefers-color-scheme:light) { :root { --shadow-sm: 0 0 0 1px oklch(0% 0 none / .04), 0 1px 2px -1px oklch(0% 0 none / .06), 0 3px 6px -2px oklch(0% 0 none / .08); --shadow-md: 0 0 0 1px oklch(0% 0 none / .05), 0 2px 4px -2px oklch(0% 0 none / .06), 0 8px 16px -4px oklch(0% 0 none / .1) } } @media (prefers-color-scheme:dark) { :root { --shadow-sm: 0 0 0 1px oklch(100% 0 none / .05), 0 1px 2px -1px oklch(0% 0 none / .34), 0 3px 6px -2px oklch(0% 0 none / .26); --shadow-md: 0 0 0 1px oklch(100% 0 none / .06), 0 2px 4px -2px oklch(0% 0 none / .38), 0 8px 16px -4px oklch(0% 0 none / .3) } }}@layer utilities { .shadow\:sm { box-shadow: var(--shadow-sm) } .shadow\:md\:hover:hover { box-shadow: var(--shadow-md) } @media print { .shadow\:none\@print { box-shadow: none } }}Keep state changes small. Moving from shadow:sm to shadow:md is usually enough for hover. Jumping from shadow:sm to shadow:2xl makes a normal card feel like a modal.
Customize mode-aware shadows
Override the same shadow-* keys in @theme light and @theme dark when your product needs softer depth, sharper enterprise surfaces, or stronger overlays. Keep the token names size-based and mode-neutral so the markup does not fork by theme.
@theme light { --shadow-sm: 0 0 0 1px oklch(0% 0 none / .05), 0 2px 4px -2px oklch(0% 0 none / .08), 0 8px 20px -6px oklch(0% 0 none / .12); --shadow-lg: 0 0 0 1px oklch(0% 0 none / .06), 0 10px 20px -8px oklch(0% 0 none / .12), 0 24px 56px -16px oklch(0% 0 none / .16);}@theme dark { --shadow-sm: 0 0 0 1px oklch(100% 0 none / .05), 0 2px 8px -4px oklch(0% 0 none / .36); --shadow-lg: 0 0 0 1px oklch(100% 0 none / .07), 0 12px 28px -10px oklch(0% 0 none / .48), 0 28px 64px -18px oklch(0% 0 none / .40);}<article class="shadow:sm">...</article><div class="shadow:lg">...</div>Promote repeated raw shadows into the scale. Keep one-off art direction in local markup or component CSS until it proves reusable. If a one-off shadow must differ by mode, prefer promoting it to a token instead of duplicating conditional raw values in markup.
Avoid elevation noise
Every panel competes for depth.
<main class="shadow:lg"> <section class="shadow:xl"> <article class="shadow:2xl">...</article> </section></main>Only the surfaces that need separation are elevated.
<main class="bg:surface-base"> <section class="bg:surface-base"> <article class="surface:raised shadow:sm">...</article> </section></main>Raw shadows become repeated product decisions without a name.
<article class="shadow:0|4px|12px|black/.12">...</article><aside class="shadow:0|4px|12px|black/.12">...</aside>Repeated decisions override a shadow scale token.
@theme light { --shadow-sm: 0 0 0 1px oklch(0% 0 none / .05), 0 4px 8px -2px oklch(0% 0 none / .08), 0 12px 24px -4px oklch(0% 0 none / .10);}@theme dark { --shadow-sm: 0 0 0 1px oklch(100% 0 none / .06), 0 8px 18px -8px oklch(0% 0 none / .44), 0 18px 40px -12px oklch(0% 0 none / .36);}<aside class="shadow:sm">...</aside>