Tokenizing Elevation Values for Consistent Depth
Part of Elevation & Shadow Tokens. Establishing a predictable depth hierarchy requires decoupling visual representation from implementation logic — specifically, abstracting z-index stacking contexts, box-shadow definitions, and transform-based parallax into a unified token registry so engineering teams eliminate visual drift across component libraries.
Prerequisites
Before implementing elevation tokens, confirm the following are in place:
- Token tier structure — your project separates primitive tokens (raw values) from semantic tokens (contextual aliases). See the three-tier naming model for the canonical approach.
- Token compiler — Style Dictionary v3+ (or equivalent: Theo, Cobalt) configured to resolve recursive alias chains.
- CSS custom property support — all target browsers support
var()(all evergreen browsers do; IE 11 requires a PostCSS fallback or polyfill). - Stylelint —
stylelint-declaration-property-value-disallowed-listplugin installed to block rawbox-shadowandz-indexvalues outside the token layer. - CI visual regression baseline — Playwright or Chromatic snapshots covering at least the surface, card, and modal elevation states.
Step-by-Step Implementation
1. Define a Base Elevation Scale (0–6)
Map primitive shadow values to CSS custom properties in a centralized registry. Use a consistent mathematical progression for blur, spread, and opacity to maintain optical balance.
/* tokens/primitives.css */
:root {
--elevation-0: none;
--elevation-1: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--elevation-2: 0 4px 6px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
--elevation-3: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
--elevation-4: 0 20px 25px rgba(0, 0, 0, 0.12), 0 10px 10px rgba(0, 0, 0, 0.06);
--elevation-5: 0 25px 50px rgba(0, 0, 0, 0.18), 0 12px 12px rgba(0, 0, 0, 0.08);
--elevation-6: 0 35px 60px rgba(0, 0, 0, 0.22), 0 15px 15px rgba(0, 0, 0, 0.1);
}
--elevation-0 uses none rather than 0 0 0 0 rgba(0,0,0,0). While the latter is technically valid, none is more readable and equally safe to transition from/to in modern browsers.
2. Alias Primitives to Semantic Tokens
Decouple the scale from usage intent. Map scale values to contextual roles consumed by components — the same pattern used in color palette architecture where raw palette entries alias to semantic roles like --color-bg-surface.
/* tokens/semantic.css */
:root {
--elevation-surface: var(--elevation-1);
--elevation-card: var(--elevation-2);
--elevation-overlay: var(--elevation-3);
--elevation-modal: var(--elevation-4);
--elevation-toast: var(--elevation-5);
--elevation-dragging: var(--elevation-6);
}
Components reference semantic tokens (--elevation-card) rather than primitives (--elevation-2), so a single edit to the alias repoints every consumer without touching component CSS.
3. Implement Fallback Chains
Use explicit fallback values in component CSS to prevent unstyled states when tokens fail to resolve — for example, when a component is loaded in an environment that hasn’t yet parsed the token file.
.component-elevated {
/* Fallback to --elevation-2 value if --elevation-card is missing */
box-shadow: var(--elevation-card, 0 4px 6px rgba(0, 0, 0, 0.08));
}
The fallback is a literal primitive value, not another var(). A var() inside a fallback resolves lazily and can silently produce an invalid value if that token is also undefined.
4. Standardize Blur, Spread, and Opacity Ratios
Integrate with the token compiler to lock shadow geometry across responsive breakpoints. Configure output transforms to emit breakpoint-specific overrides where optical scaling is needed — for instance, high-DPI displays often warrant slightly reduced blur to avoid muddiness.
{
"elevation": {
"4": {
"value": "0 20px 25px rgba(0,0,0,0.12), 0 10px 10px rgba(0,0,0,0.06)",
"type": "boxShadow",
"attributes": {
"category": "elevation",
"level": 4,
"role": "modal"
}
}
}
}
Including type and attributes lets the compiler generate platform-specific outputs (Android elevation dp, iOS shadowRadius) from the same source.
5. Validate Token Resolution in Theme Modes
Run automated computed style assertions. Ensure prefers-color-scheme or data-attribute themes correctly invert shadow opacity without breaking stacking contexts.
// playwright/elevation.spec.js
test('modal elevation resolves correctly in dark mode', async ({ page }) => {
await page.emulateMedia({ colorScheme: 'dark' });
await page.goto('/components/modal');
const shadow = await page.locator('.modal').evaluate(
el => getComputedStyle(el).boxShadow
);
expect(shadow).not.toBe('none');
expect(shadow).toContain('rgba');
});
Verification
After completing the implementation steps, confirm the following:
- Token resolution in DevTools — open the Computed panel for a
.modalelement. Thebox-shadowvalue should be the resolved string from--elevation-4, not the literalvar()text. If you seevar(--elevation-modal)in Computed, the token file loaded after paint; check stylesheet order. - CI log pattern — a successful build with full alias resolution produces output like:
[INFO] Style Dictionary: 6 primitive elevation tokens registered. [INFO] Style Dictionary: 6 semantic elevation aliases resolved. [SUCCESS] Token AST validated. 42 semantic aliases resolved. - Visual regression diff — run
npx playwright test --update-snapshotsonce against a known-good state, then re-run without the flag on subsequent PRs. Any diff in shadow area indicates an unintended token change. - Stylelint gate — run
npx stylelint "src/**/*.css". Expect zero violations forbox-shadoworz-indexraw values in component files.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
undefined CSS variable in build output |
Token compiler fails to resolve recursive alias chain; likely a missing or misnamed primitive. | Run npm run tokens:build -- --verbose to inspect intermediate output. Ensure every alias terminates at a primitive value. Enforce JSON schema validation with npx ajv validate -s schema.json -d tokens.json before compilation. |
| Visual regression flags depth mismatches despite passing unit tests | Browser-specific shadow rendering differences (WebKit vs Blink) or missing @supports fallbacks for complex box-shadow stacks. |
Inject computed style snapshots into CI artifacts. Use Playwright getComputedStyle() assertions to validate exact box-shadow strings against token baselines. |
| Token drift detected in PR reviews | Manual overrides in component CSS bypassing the token registry (e.g., inline z-index: 9999 or hardcoded shadows). |
Enforce Stylelint declaration-property-value-disallowed-list to block raw box-shadow and z-index values outside the token layer. Fail PRs on violation. |
| Shadows disappear in forced-colors mode | Windows High Contrast Mode strips box-shadow entirely. |
Replace with outline under @media (forced-colors: active). Elevation intent is communicated through border/outline, not shadow. |
| Dark-mode shadows appear too harsh | Primitive opacity values calibrated for light backgrounds only. | Add a [data-theme="dark"] :root override block that reduces the alpha channel on each primitive by ~30–40%, then re-alias semantics to the same token names. |
Migration Note
Migrating legacy elevation implementations requires a phased, automated approach to prevent regression and maintain developer velocity.
-
Audit — extract all hardcoded
box-shadow,z-index, andtranslateZvalues using AST parsing. Run a Stylelint pass across the codebase to generate an inventory report:{ "components/Modal.css": ["box-shadow: 0 10px 30px rgba(0,0,0,0.15)", "z-index: 1000"], "components/Card.tsx": ["boxShadow: '0 4px 12px rgba(0,0,0,0.08)'"] } -
Map — create a translation matrix matching legacy values to the nearest semantic elevation token. Use perceptual distance algorithms or manual design review to align legacy shadows with the new scale.
-
Refactor — apply codemods to replace inline values with
var(--elevation-*)references. Preserve legacy fallbacks during the transition period using CSS@layeror feature queries:npx jscodeshift -t ./codemods/elevation-tokenizer.js src/ --extensions=tsx,css -
Verify — run parallel builds with legacy and tokenized outputs. Compare computed styles using Playwright or visual regression suites to confirm zero optical regression.
-
Deprecate — remove legacy shadow definitions and enforce token-only usage via CI lint gates and pre-commit hooks (
husky+lint-staged). Archive the translation matrix for historical reference.
Cross-Environment Validation & Maintenance
Maintain elevation consistency by integrating token validation into pre-commit hooks and CI pipelines. Use token diffing tools to detect unauthorized scale modifications and enforce semantic naming conventions. Document resolution patterns for edge cases, including high-DPI display scaling, prefers-reduced-motion constraints, and forced-colors mode compliance. Regularly audit token consumption metrics to identify unused or duplicated elevation references, ensuring the registry remains lean, accessible, and predictable across all delivery channels.
Related
- Elevation & Shadow Tokens — parent reference covering the full shadow token taxonomy and naming model
- Color Palette Architecture — the same primitive-to-semantic aliasing pattern applied to color tokens
- Token Fundamentals & Naming Conventions — pillar overview covering the three-tier token hierarchy that elevation tokens sit within