Skeuomorphism — Elevation & Material Tokens
The material system adds an elevation tier on top of the existing colour/radius/type tokens. Everything lives in CSS variables (light + dark) so components stay theme-agnostic. Source of truth: apps/web/app/globals.css; Tailwind mappings in apps/web/tailwind.config.ts.
1. The two material colours
| Token | Role | Light | Dark |
|---|---|---|---|
--tactile-hi | Top-edge highlight (the "light") | 0 0% 100% | 210 40% 96% |
--tactile-lo | Ambient shadow hue (cool, not black) | 222 45% 18% | 222 70% 1% |
Shadows use a cool hue rather than pure black so they read as soft ambient occlusion, not a hard drop.
2. Elevation scale
Each token is a multi-layer shadow: an inset top highlight + 2–3 ambient layers (or inset layers for wells). Values differ per theme (dark uses deeper, higher-alpha ambient and a fainter highlight).
| Token | Tailwind | Meaning | Used by |
|---|---|---|---|
--shadow-raised | shadow-raised | Resting raised surface | Cards, buttons, KPI tiles |
--shadow-raised-hover | shadow-raised-hover | Lifted (hover) | Buttons on :hover |
--shadow-floating | shadow-floating | Transient top layer | Dialogs, popovers, toasts, drag ghost |
--shadow-well | shadow-well | Carved-in recess | Inputs, selects, textareas, tracks |
--shadow-pressed | shadow-pressed | Pressed-in (active) | Buttons on :active |
Light values (abridged)
--shadow-raised:
inset 0 1px 0 0 hsl(var(--tactile-hi) / 0.85), /* top sheen */
0 1px 1.5px -0.5px hsl(var(--tactile-lo) / 0.14), /* contact */
0 3px 6px -1px hsl(var(--tactile-lo) / 0.10), /* mid */
0 10px 22px -8px hsl(var(--tactile-lo) / 0.10); /* ambient */
--shadow-well:
inset 0 1.5px 3px 0 hsl(var(--tactile-lo) / 0.15), /* carved */
inset 0 1px 1px 0 hsl(var(--tactile-lo) / 0.08),
0 1px 0 0 hsl(var(--tactile-hi) / 0.6); /* base edge */
--shadow-pressed:
inset 0 2px 5px 0 hsl(var(--tactile-lo) / 0.22),
inset 0 1px 2px 0 hsl(var(--tactile-lo) / 0.14);
Dark mode overrides all five with higher ambient alpha (0.5–0.7) and a faint highlight (0.06–0.12).
3. Material overlays (utility classes)
| Class | Effect |
|---|---|
.sheen | Layers --sheen-top (a top gloss gradient) over the element's own bg-color. Apply to any raised surface. |
.glass | Frosted chrome: translucent surface + backdrop-filter: blur(14px) saturate(180%) + hairline border. Has a @supports fallback to a near-opaque surface. |
--sheen-top: linear-gradient(180deg, hsl(var(--tactile-hi) / 0.5), transparent 34%);
4. Composition patterns
| Pattern | Recipe |
|---|---|
| Raised panel | bg-surface sheen shadow-raised border border-border/70 rounded-lg |
| Recessed field | bg-surface-sunken shadow-well → on focus bg-surface shadow-none ring-2 ring-ring |
| Tactile button | sheen shadow-raised → hover:-translate-y-px hover:shadow-raised-hover → active:translate-y-px active:shadow-pressed |
| Floating overlay | glass shadow-floating rounded-lg |
5. Rules
- Never write a raw
box-shadow/drop-shadowin a component — use a token class. - A surface is either raised or a well — never both.
.sheenonly on surfaces that also carryshadow-raised(a highlight with no depth looks wrong)..glassonly on chrome/overlays that sit above scrolling content — not on the content itself (blur cost).- Keep total shadow layers ≤ 4 per element (perf + subtlety).