Elevation & Shadow Tokens
Architectural Foundations for Depth Management
Elevation and shadow tokens establish a predictable visual hierarchy by abstracting CSS box-shadow and filter: drop-shadow() declarations into semantic, reusable values. When integrated with broader Design System Token Fundamentals & Naming Conventions, these tokens enable consistent depth scaling across component libraries. The architecture relies on a tiered naming strategy that maps directly to z-index stacking contexts and optical layering requirements.
Architecture Patterns & Trade-offs
A production-ready depth architecture separates primitive tokens (raw CSS values) from semantic tokens (contextual usage). Primitives define the physical properties (--shadow-blur-sm, --shadow-color), while semantics map to UI states (--elevation-surface, --elevation-modal).
Architectural Trade-offs:
- Primitive vs. Semantic Coupling: Hardcoding primitives directly into components offers faster initial development but creates maintenance debt during theme migrations. Semantic abstraction requires upfront registry configuration but enables zero-touch theme switching.
box-shadowvs.filter: drop-shadow():box-shadowperforms optimally for rectangular surfaces and is rendered on the compositor in most browsers.drop-shadow()respects alpha channels and complex paths (e.g., clipped SVG) but triggers a more expensive paint operation. The recommended pattern usesbox-shadowfor 95% of UI surfaces and reservesfilter: drop-shadow()for iconography and SVG overlays.- Z-Index Isolation: Elevation tokens should not be conflated with
z-indexvalues. Use CSS stacking contexts (isolation: isolateortransform: translateZ(0)) to contain depth layers, preventing globalz-indexwars and ensuring predictable rendering order.
Token Definition & Implementation Workflow
The implementation pipeline begins with defining base shadow primitives (e.g., --shadow-sm, --shadow-md) and mapping them to semantic elevation levels (--elevation-raised, --elevation-overlay). These values must be synchronized with Spacing & Layout Tokens to ensure proportional depth-to-distance ratios. Engineers should utilize CSS custom properties with fallback chains and PostCSS plugins for cross-browser normalization. Each token is exported via a centralized JSON/YAML configuration, compiled into CSS variables, and injected into the design token registry.
Framework-Agnostic Implementation
The following JSON structure demonstrates a production-ready token definition aligned with Style Dictionary conventions:
{
"elevation": {
"primitive": {
"offset-y-sm": { "value": "2px" },
"blur-sm": { "value": "8px" },
"spread": { "value": "0px" },
"color-light": { "value": "rgba(0, 0, 0, 0.12)" }
},
"semantic": {
"surface": {
"value": "0px 2px 8px 0px rgba(0, 0, 0, 0.12)"
},
"raised": {
"value": "0px 4px 12px 0px rgba(0, 0, 0, 0.15)"
},
"overlay": {
"value": "0px 12px 32px 0px rgba(0, 0, 0, 0.25)"
}
}
}
}
Compiled into CSS custom properties, the output enables framework-agnostic consumption:
:root {
--elevation-surface: 0px 2px 8px 0px rgba(0, 0, 0, 0.12);
--elevation-raised: 0px 4px 12px 0px rgba(0, 0, 0, 0.15);
--elevation-overlay: 0px 12px 32px 0px rgba(0, 0, 0, 0.25);
}
.card {
box-shadow: var(--elevation-surface);
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
Workflow Optimization: Decouple token generation from framework-specific bundlers. Use a standalone token compiler (e.g., Style Dictionary) that outputs CSS variables, SCSS maps, and JS objects simultaneously. This ensures React, Vue, and vanilla implementations consume identical depth values without duplication.
Validation Pipeline & CI/CD Integration
Automated validation ensures elevation tokens maintain optical consistency and performance constraints. The pipeline runs Stylelint rules to detect hardcoded shadow values and verifies that shadow spread does not exceed layout boundaries. Integration with visual regression testing (e.g., Chromatic) guarantees that elevation changes propagate correctly across themes before deployment.
CI/CD Pipeline Configuration
name: Token Validation & Shadow Audit
on:
pull_request:
paths: ['tokens/**', 'styles/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with: { node-version: '20' }
- name: Install Dependencies
run: npm ci
- name: Lint Tokens & CSS
run: npm run lint:tokens && npm run lint:css
- name: Run Visual Regression
uses: chromaui/action@v1
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
buildScriptName: 'chromatic:build'
Stylelint Configuration for Shadow Enforcement
Hardcoded shadows bypass the token registry and break theme consistency. The following .stylelintrc.json prevents raw box-shadow values in component CSS:
{
"rules": {
"declaration-property-value-disallowed-list": {
"box-shadow": ["/^\\d/", "/^rgba/", "/^rgb/"],
"filter": ["/^drop-shadow/"]
},
"custom-property-pattern": "^--(elevation|shadow)-[a-z0-9-]+$"
}
}
This uses the built-in declaration-property-value-disallowed-list rule, which rejects any box-shadow value that does not go through a var() reference.
Pipeline Trade-offs:
- Strict vs. Lenient Linting: Enforcing zero hardcoded shadows on legacy codebases causes CI failures. Implement a
--allow-hardcodedescape hatch with a deprecation warning during migration phases. - Visual Regression Overhead: Chromatic captures elevation changes accurately but increases CI runtime. Optimize by scoping snapshot tests to elevation-dependent components (modals, dropdowns, cards) rather than full-page renders.
Advanced Architecture: Dynamic Depth & Theme Adaptation
Modern design systems require elevation tokens to adapt dynamically to dark mode, high-contrast modes, and responsive breakpoints. By decoupling shadow opacity from color values, architects can maintain perceptual depth without duplicating token sets. For granular control over blur and spread parameters, refer to the methodology for Tokenizing elevation values for consistent depth.
Dynamic Theme Implementation
Dark mode elevation requires a perceptual shift: shadows become less visible on dark backgrounds, necessitating either increased opacity, added ambient light layers, or border-based depth cues.
:root {
--elevation-color: rgba(0, 0, 0, 0.15);
--elevation-border: none;
--elevation-surface: 0px 2px 8px 0px var(--elevation-color);
}
@media (prefers-color-scheme: dark) {
:root {
/* Shift to subtle ambient glow + border for WCAG compliance */
--elevation-color: rgba(255, 255, 255, 0.08);
--elevation-border: 1px solid rgba(255, 255, 255, 0.12);
}
}
[data-theme="dark"] {
--elevation-color: rgba(255, 255, 255, 0.08);
--elevation-border: 1px solid rgba(255, 255, 255, 0.12);
}
.surface {
box-shadow: var(--elevation-surface);
border: var(--elevation-border, none);
background: var(--color-surface);
}
Architectural Considerations
- Opacity Decoupling: Storing shadow color and alpha separately (e.g.,
--shadow-color: rgba(0, 0, 0, 0.15)) enables programmatic alpha adjustments per theme without duplicating blur/spread values. - High-Contrast Mode (HCM): Windows and macOS HCM strips shadows entirely. Implement
@media (forced-colors: active)to replacebox-shadowwithoutlineorborderproperties, ensuring WCAG 2.2 compliance. - Motion Integration: Elevation state changes must align with motion tokens. Use
transition: box-shadow 0.2s var(--ease-out-expo)to synchronize depth shifts with layout animations, preventing visual jank during hover/focus states. - Performance Boundaries: Excessive blur radii trigger software rasterization on some hardware. Cap
blurat32pxand usewill-change: box-shadowsparingly on interactive elements to maintain 60fps compositing.