Skip to main content

0005 — Elevation & shadows (when a surface gets a drop shadow)

Status: Accepted · Date: 2026-06-26

Context

Shadows were applied inconsistently: peer surfaces on the same screen sometimes had a drop shadow and sometimes didn't, with no shared rule — so the UI read as a "weird mix" of 2-D (flat) and 3-D (raised) blocks.

There was no documented rule, but the design system already encodes elevation deliberately. packages/ui/src/styles/design-system.css defines three intent-named shadow tokens — --shadow-prominent ("raised cards"), --shadow-sidebar ("tighter elevation for the sidebar / floating drawer cards"), --shadow-floating ("higher elevation for floating UI … visibly hovers above content") — plus Tailwind's shadow-2xl for modals. The tracking app uses them consistently by role (e.g. session-stats and the active set card use shadow-prominent; FABs / action toolbars / banners use shadow-floating; dialogs use shadow-2xl; the station rails use shadow-sidebar). The rule was real but unwritten. This ADR writes it down and makes it the norm for both apps.

Decision

A surface gets a shadow only to signal it sits on a layer above what is directly behind it. The bigger the elevation jump, the bigger the shadow. Flat (border only) is the default.

Pick the token by role:

RoleTokenExamples
In-flow grouped rows / settings / list cardnone (border only)list-rows Card, detail rows, sections
Raised primary content container on the page backgroundshadow-prominentdata-table card, stat / summary cards, ranking cards, the active/selected card, horizontal scrollers
Drawer / sidebar / rail surfaceshadow-sidebarthe sliding aside, station rails
Floating chrome that hovers over contentshadow-floatingFABs, floating action toolbars/pills, banners/toasts, overlay CTAs
Modal / dialog / full-screen overlay over a backdropshadow-2xlconfirm dialogs, full-screen overlays

Two principles govern application:

  1. Default flat. Add a shadow to communicate elevation, never for decoration. Border-only is the baseline.
  2. Peer consistency. All surfaces in the same role on the same screen get the same treatment. Most "weird mix" is a peer-consistency violation, not a missing token — e.g. a table card with shadow-prominent next to flat sibling content cards.

The two in-flow card tiers are intentional and must not be conflated:

  • Grouped-rows / settings Card (@enode/ui/list-rows) → flat. The iOS-settings list vocabulary; lives inside drawers/sections.
  • Primary content container (a card that is a feature's content on the page background) → shadow-prominent.

Consequences

  • New raised content containers use shadow-prominent; new floating chrome uses shadow-floating / shadow-2xl; grouped-row cards stay flat. Don't invent one-off shadow-* values — use the tokens.
  • Reviewers reject peer-inconsistent elevation (one content card raised, its sibling flat) the same way they reject ad-hoc colours.
  • Applied first to the portal dashboard (summary, ranking, and table cards normalised to shadow-prominent); other screens follow as touched.
  • See the "Elevation & shadows" section of components.md for the day-to-day digest.