Skip to main content

Learning Track

Filter content by your role to see the most relevant sections

Elevation & Shadow Foundations

Design depth as a system: a calibrated shadow ramp, semantic surface roles, the z-axis contract between elevation and layering, and the dark-mode and performance realities that shadows carry with them.

Darian Rosebrook
Staff Design Technologist, Design Systems Architect
Expertise:Design Systems, Visual Design, Performance

Why This Matters

Elevation is the most misused depth cue in interface design. Shadows get added to make things "pop", and within a year every card floats, every button hovers, and users can no longer read which surfaces are above which. Depth is a communication channel: it tells users what overlays what, what is attached to what, and what is interactive. Spent everywhere, it says nothing anywhere.

The system answer is the same as color's: a small, calibrated ramp where each step means something, consumed through semantic surface roles. Elevation has two extra realities to manage that color does not—shadows are nearly invisible on dark surfaces (dark modes need alternative depth cues), and shadows are real render cost (blur radius is paint area, and layered shadows stack).

This page covers the elevation system as built here: the elevation.level ramp in the core tokens, the semantic surface roles above it, and the effect.backdropBlur companions.

Core Concepts: The Ramp, the Roles, the Z-Axis

Four Levels, Zero to Three

The core ramp in ui/designTokens/core/elevation.tokens.json is four steps—flat to floating—and each is a complete shadow, not a number to scale:

// elevation.level (values) — offsetX / offsetY / blur / color
level.0:  0px  0px   0px   (no shadow — flat with its surface)
level.1:  0px  1px   3px   rgba(0,0,0,0.12)  // resting card
level.2:  0px  3px   6px   rgba(0,0,0,0.14)  // raised/floating
level.3:  0px  8px  16px   rgba(0,0,0,0.18)  // modal overlay

Read the ramp as a physics claim. Offset grows faster than blur, and opacity edges up as blur widens—soft light sources close to the surface. The consistency across steps is the point: a level-3 dialog and a level-1 card look like the same world at different heights, not like two design decisions.

Alongside the levels sit the parts they were built from—elevation.offset.y1/y2 (1px, 3px), elevation.blur.sm/md (3px, 6px), elevation.spread.none—and a parallel elevation.depth.0–4 number scale for logical layering, which is how z-index decisions stay off the shadow ramp (more below).

Semantic Surfaces Name the Intent

The semantic layer maps levels to roles, exactly as color maps ramps to intent:

// ui/designTokens/semantic/elevation.tokens.json (excerpt)
"elevation": {
  "none":     { "$value": "none" },          // explicitly removes shadow
  "default":  { "$value": "{elevation.level.1}" },
  "surface": {
    "raised":   { "$value": "{elevation.level.1}" },
    "floating": { "$value": "{elevation.level.2}" }
  },
  "depth":    { "$value": "{elevation.depth.0}" }
}

Components consume surface.raised or surface.floating, never level.2 directly. The payoff is the same as every semantic layer: when the design decision changes—floating surfaces gain a hairline border in dark mode, say—it changes in one place.

Elevation Is a Promise About the Z-Axis

A shadow claims "I am above my surroundings." That claim has two halves that must agree: the visual shadow, and the stacking order (z-index) that makes overlapped rendering match. The system keeps them separate on purpose—elevation.depth numbers for stacking, elevation.level shadows for appearance—because they change at different rates, but the review rule is rigid: a surface's depth number must order with its shadow level. A level-3 modal shadow under a level-1 nav bar is a lie users can see.

Dark Mode and the Shadow Problem

Shadows are a light-mode artifact: on near-black surfaces, black-at-12% is invisible. The honest system response is not heavier shadows but alternative depth cues that the semantic layer can swap per mode—surface color steps (the background.primary → secondary ramp from the color system), hairline borders, and the effect.backdropBlur companions (4/8/12px) for surfaces that sit over content. Because components consume surface.floating rather than a raw shadow value, the dark-mode story can evolve without touching them.

System Roles: Where Elevation Decisions Land

Design Impact

Designers own the meaning table: which surfaces are flat, which are raised, which float. The discipline that keeps it honest is scarcity—elevation is for things that must read as above: overlays, popovers, dragged elements. A marketing card that wants a shadow for warmth wants a border or a surface step, not elevation.

Engineering Impact

Engineers own the z-axis agreement and the render cost: depth numbers that order with shadow levels, shadows referenced through scoped tokens, and awareness that blur radius is paint area—a full-screen level-3 blur behind every list row is a performance decision, not a style line.

Accessibility Impact

Accessibility owns the rule that elevation never carries information alone: depth supplements structure that semantics already express (a dialog is a dialog because of roles and focus trapping, not its shadow). Users with low vision or high-contrast modes may see no shadows at all—the interface must survive that.

Design & Code Interplay

In the design tool, the elevation system appears as four named effects matching the levels exactly—same offsets, blurs, and opacities—with the surface roles as the styles components actually apply. The comp review question is "which surface role is this?" and the answer has to come from the meaning table, not from which shadow looked nice on the artboard.

Dark-mode design files carry the second half of the contract: each elevated surface shown twice—once per mode—with its alternative cue visible. A surface that only works with its shadow is a dark-mode defect shipped in daylight.

Applied Example: A Select Menu, Elevated Correctly

Ship the classic elevation component—a select menu that opens over a form—using the whole stack:

  1. Assign the surface role: the open menu is surface.floating (level 2: 0/3/6 at 14%)—above the page, below a modal. The trigger itself stays flat; the control does not elevate, its overlay does.
  2. Match the stacking: the popover layer gets the elevation.depth number that orders above content and below modals, so overlap rendering and the shadow tell the same story.
  3. Dark mode alternative: in dark theme the menu's panel uses a background step plus hairline border as its primary cue; the level-2 shadow remains but does not carry the meaning alone.
  4. Enter with motion tokens, exit faster: the menu enters on motion.interaction.enter (medium1 + soft.enter) and exits on motion.interaction.exit (short3 + quick.exit), with the prefers-reduced-motion block fading only—no scale, no travel.
  5. Cost check: one blurred layer the size of the menu, not the viewport. Blur is paint area; menus are small; this is why the ramp tops out at 16px of blur.

Note how many foundations one component touches—elevation, color surfaces, motion, depth—and how each contributed a named token instead of a local decision. That is the compounding return of tokenized foundations.

Constraints & Trade-offs

  • Four levels vs expressive depth: dramatic hero moments may want shadows the ramp cannot say. The system answer is a new level with review—ramps grow by decision—never an ad-hoc box-shadow in a feature file.
  • Shadows vs dark-mode legibility: the light-mode ramp is nearly invisible on dark surfaces; alternative cues (surface steps, borders) must be designed and audited per mode rather than assumed.
  • Depth-as-numbers vs z-index literals: maintaining a parallel depth scale costs a little indirection and buys stack ordering that can be reviewed against shadow levels; raw z-index: 9999 buys nothing but archaeology.
  • Backdrop blur vs performance: effect.backdropBlur (4/8/12px) is a beautiful depth cue and one of the most expensive effects in the paint pipeline—budgeted by scale and placement, not applied as garnish.

Common Pitfalls & Failure Modes

1. Everything floats

/* Bad — Depth as decoration: the page whispers constantly */
.card, .badge, .button { box-shadow: var(--core-elevation-level-1); }
/* Good: Elevation for overlays and detachment; borders for the rest */

2. Hand-tuned shadows

/* Bad: A fifth shadow nobody calibrated */
.popover { box-shadow: 0 2px 10px rgba(0,0,0,0.2); }
/* Good: The ramp, through the semantic role */
.popover { box-shadow: var(--semantic-elevation-surface-floating); }

3. Shadows and stacking disagreeing

/* Bad: Level-3 shadow pinned under a level-1 bar */
.modal { box-shadow: var(--core-elevation-level-3); z-index: 10; }
.nav   { z-index: 100; }
/* Good: Depth numbers order with shadow levels */

4. Depth as the only signal

If removing every shadow breaks the interface's meaning, elevation was carrying semantics alone. Dialogs are dialogs because of roles, focus, and interaction—shadows only corroborate.

5. Viewport-sized blur

/* Bad: Full-screen frost behind every list row */
.row::backdrop { backdrop-filter: var(--core-effect-backdrop-blur-lg); }
/* Good: Blur scaled to the surface that needs it */

Elevation System Health Metrics

Signal 1: Ramp exclusivity

  • Healthy: every box-shadow in product CSS resolves through the ramp (via semantic surface roles or scoped contracts); a grep for box-shadow literals returns only fallbacks.
  • Warning: one or two hand-tuned shadows—each a fifth level nobody calibrated, and a future dark-mode question with no answer.
  • Critical: bespoke shadows outnumber ramp references; elevation no longer means anything and users are back to guessing what floats.

Signal 2: Depth agreement

  • Healthy: z-index values derive from the elevation.depth scale and order with the shadow levels; the overlay stack is reviewable as one table.
  • Warning: a few z-index: 999 escapes—archaeology waiting, each one hiding a disagreement between what is above and what looks above.
  • Critical: stacking is managed by escalation (raise the number until it works); the shadow story and the stacking story are different products.

Signal 3: Dark-mode cue coverage

  • Healthy: every elevated surface has a per-mode depth cue beyond shadow (surface step or border), auditable by flipping modes on the overlay inventory.
  • Warning: new overlays ship with light-mode-only cues—the debt is invisible until someone flips the theme with a dialog open.
  • Critical: dark mode's overlays are indistinguishable from the page—depth carrying meaning that has silently vanished for a whole theme.

Migration Strategy: Taming the Shadow Stack

  1. Inventory shadows and stacking: every box-shadow, every z-index, with the surface it belongs to. The inventory usually reveals three clusters of shadows pretending to be seven and a stacking story written by escalation.
  2. Map to the meaning table: each surface gets a role—flat, raised, floating, modal— and the shadow snaps to the ramp level that role names. Unmappable shadows were decoration; they become borders or surface steps.
  3. Re-derive stacking from depth tokens: replace raw z-indexes with the depth scale, largest for the topmost role; the table from step 1 becomes the review artifact that proves shadows and stacking agree.
  4. Add the dark-mode cue per overlay: migration is the moment each surface gains its per-mode alternative—the work is cheap once per surface and permanent.
  5. Sweep for literals: the box-shadow grep joins the per-directory lint as each area cleans, so the fifth shadow cannot quietly return.

Real-World Case Studies

Case 1: The modal under the navbar

A modal shadowed at level 3 rendered beneath the sticky nav's z-index: 100. Sighted users saw the dimmed page above their dialog's header—a depth lie in two channels at once. The depth- token re-derivation ordered stacking with shadows, and the bug class (overlay-under-chrome) stopped recurring because the review checks one table.

Case 2: The card that wanted to pop

Marketing asked for "more presence" on a card and got a hand-tuned shadow—then a second card got a different one. Six weeks later the grid was a weather system. The meaning-table review returned both cards to surface.raised and gave one of them a border instead: presence via a channel that meant presence.

Case 3: The dark theme that lost its dialogs

Dark-mode QA filed "dialog invisible"—the level-2 shadow had vanished against near-black, and nothing else distinguished the panel. The surface-step plus hairline cure took one semantic addition; the finding became the dark-mode cue coverage signal, auditable by flipping themes across the overlay inventory.

Verification Checklist

Additional Resources

  • Radius & Shape — the other half of surface identity (/blueprints/foundations/radius)
  • Motion & Duration — the tokens elevated surfaces enter and exit with (/blueprints/foundations/motion)
  • Material Design elevation guidance — the reference articulation of elevation as a z-axis system (https://m3.material.io/styles/elevation/overview)
  • The sources — ui/designTokens/core/elevation.tokens.json, ui/designTokens/semantic/elevation.tokens.json, ui/designTokens/core/effect.tokens.json

Related Concepts

Reflection Questions