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:
| Role | Token | Examples |
|---|---|---|
| In-flow grouped rows / settings / list card | none (border only) | list-rows Card, detail rows, sections |
| Raised primary content container on the page background | shadow-prominent | data-table card, stat / summary cards, ranking cards, the active/selected card, horizontal scrollers |
| Drawer / sidebar / rail surface | shadow-sidebar | the sliding aside, station rails |
| Floating chrome that hovers over content | shadow-floating | FABs, floating action toolbars/pills, banners/toasts, overlay CTAs |
| Modal / dialog / full-screen overlay over a backdrop | shadow-2xl | confirm dialogs, full-screen overlays |
Two principles govern application:
- Default flat. Add a shadow to communicate elevation, never for decoration. Border-only is the baseline.
- 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-prominentnext 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 usesshadow-floating/shadow-2xl; grouped-row cards stay flat. Don't invent one-offshadow-*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.