Browse documents

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

TokenRoleLightDark
--tactile-hiTop-edge highlight (the "light")0 0% 100%210 40% 96%
--tactile-loAmbient 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).

TokenTailwindMeaningUsed by
--shadow-raisedshadow-raisedResting raised surfaceCards, buttons, KPI tiles
--shadow-raised-hovershadow-raised-hoverLifted (hover)Buttons on :hover
--shadow-floatingshadow-floatingTransient top layerDialogs, popovers, toasts, drag ghost
--shadow-wellshadow-wellCarved-in recessInputs, selects, textareas, tracks
--shadow-pressedshadow-pressedPressed-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)

ClassEffect
.sheenLayers --sheen-top (a top gloss gradient) over the element's own bg-color. Apply to any raised surface.
.glassFrosted 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

PatternRecipe
Raised panelbg-surface sheen shadow-raised border border-border/70 rounded-lg
Recessed fieldbg-surface-sunken shadow-well → on focus bg-surface shadow-none ring-2 ring-ring
Tactile buttonsheen shadow-raisedhover:-translate-y-px hover:shadow-raised-hoveractive:translate-y-px active:shadow-pressed
Floating overlayglass shadow-floating rounded-lg

5. Rules

  1. Never write a raw box-shadow/drop-shadow in a component — use a token class.
  2. A surface is either raised or a well — never both.
  3. .sheen only on surfaces that also carry shadow-raised (a highlight with no depth looks wrong).
  4. .glass only on chrome/overlays that sit above scrolling content — not on the content itself (blur cost).
  5. Keep total shadow layers ≤ 4 per element (perf + subtlety).