# Design System — Token Reference > Source of truth: `src/app.css` > Last updated: 2026-03-27 All UI values must come from these tokens unless listed under [Intentional Raw Values](#intentional-raw-values). --- ## Spacing 4px grid. Token name = multiplier (`--sp-3` = 3 × 4px = 12px). | Token | Value | Use for | |-------|-------|---------| | `--sp-0` | 0px | Explicit zero | | `--sp-px` | 1px | Borders, hairlines | | `--sp-0.5` | 2px | Micro-nudge (margin-top on meta text) | | `--sp-1` | 4px | Tight gap, field label gap, small padding | | `--sp-1.5` | 6px | Badge gap, icon gap, footer gap | | `--sp-2` | 8px | Compact gap, button group gap, inner padding | | `--sp-3` | 12px | Standard gap, row padding, list item gap | | `--sp-4` | 16px | Card padding (mobile), section margin, tab margin | | `--sp-5` | 20px | Card padding (desktop), module gap, sidebar gap | | `--sp-6` | 24px | Large padding, overlay padding, page container | | `--sp-7` | 28px | Primary card padding, section group margin | | `--sp-8` | 32px | Page top padding, empty state, desktop grid gap | | `--sp-10` | 40px | Large elements (avatar width), empty list padding | | `--sp-12` | 48px | Empty state padding, large spacing | | `--sp-16` | 64px | Reserved | | `--sp-20` | 80px | Page bottom padding (scroll clearance) | ### Semantic spacing aliases | Token | Resolves to | Use for | |-------|-------------|---------| | `--section-gap` | `--sp-7` (28px) | Gap between major page sections | | `--card-pad` | `--sp-5` (20px) | Default module/card padding | | `--card-pad-primary` | `--sp-7` (28px) | Hero/primary module padding | | `--card-pad-secondary` | `--sp-4` (16px) | Compact card padding | | `--row-gap` | `--sp-3` (12px) | Gap between list rows | | `--module-gap` | `--sp-5` (20px) | Gap between dashboard modules | | `--row-pad-y` | 14px | Vertical padding inside data rows (off-grid, intentional) | | `--row-pad-x` | `--sp-4` (16px) | Horizontal padding inside data rows | | `--inner-gap` | `--sp-3` (12px) | Gap between items within a row | ### Mobile overrides (≤768px) | Token | Desktop | Mobile | |-------|---------|--------| | `--card-pad` | 20px | 16px | | `--card-pad-primary` | 28px | 20px | | `--row-pad-y` | 14px | 16px | | `--section-gap` | 28px | 20px | --- ## Radius | Token | Value | Use for | |-------|-------|---------| | `--radius-xs` | 4px | Tiny pills, skeleton placeholders, kbd hints | | `--radius-sm` | 6px | Badges, chips, nav links, danger buttons | | `--radius-md` | 8px | Buttons, inputs, tabs, entry rows, icon containers | | `--radius` | 12px | Cards, modals, panels, main containers | | `--radius-lg` | 16px | Hero cards, action cards, pill chips, ImmichPicker modal | | `--radius-full` | 9999px | Circles, toggles, avatars | --- ## Elevation (Shadows) Light and dark mode have separate values. Dark mode uses higher opacity. | Token | Light | Use for | |-------|-------|---------| | `--shadow-xs` | `0 1px 2px rgba(0,0,0,0.03)` | Row hover, active tabs, inner elements | | `--shadow-sm` | `0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.04)` | Secondary cards, inputs, budget tables | | `--shadow-md` | `0 2px 6px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06)` | Standard card elevation (default `.module`) | | `--shadow-lg` | `0 4px 12px rgba(0,0,0,0.06), 0 16px 40px rgba(0,0,0,0.1)` | Hero cards, primary modules, dropdowns | | `--shadow-xl` | `0 8px 24px rgba(0,0,0,0.08), 0 24px 60px rgba(0,0,0,0.15)` | Modals, overlay panels | **Legacy aliases**: `--card-shadow` → `--shadow-md`, `--card-shadow-sm` → `--shadow-sm` --- ## Colors ### Surfaces (3-layer depth) | Token | Light | Dark | Layer | |-------|-------|------|-------| | `--canvas` | `#F5F6F8` | `#09090b` | Page background — everything sits on this | | `--surface` | `#FFFFFF` | `#0f0f12` | Sidebars, panels, slide-out sheets | | `--surface-secondary` | `#FAFAFB` | `#111114` | Input backgrounds, secondary panels | | `--card` | `#FFFFFF` | `#161619` | Content containers, elevated with shadow | | `--card-secondary` | `#FAFAFB` | `#111114` | Secondary cards, button backgrounds | | `--card-hover` | `#f0f0f3` | `#1c1c20` | Row hover, interactive feedback | ### Borders | Token | Light | Dark | |-------|-------|------| | `--border` | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.06)` | | `--border-strong` | `rgba(0,0,0,0.1)` | `rgba(255,255,255,0.1)` | ### Text hierarchy | Token | Light | Dark | Use for | |-------|-------|------|---------| | `--text-1` | `#1a1a1f` | `#fafafa` | Headings, names, amounts — read first | | `--text-2` | `#4a4a55` | `#a1a1aa` | Body text, descriptions — read second | | `--text-3` | `#6b6b76` | `#71717a` | Labels, metadata, captions — supporting | | `--text-4` | `#b4b4bd` | `#3f3f46` | Placeholders, disabled, timestamps — background | ### Accent (indigo / blue) | Token | Light | Dark | Use for | |-------|-------|------|---------| | `--accent` | `#4F46E5` | `#3b82f6` | Primary actions, links, active states | | `--accent-bg` | `#EEF2FF` | `rgba(59,130,246,0.1)` | Icon wells, strong highlight backgrounds | | `--accent-dim` | `rgba(79,70,229,0.06)` | `rgba(59,130,246,0.08)` | Subtle hover, selection backgrounds, focus rings | | `--accent-border` | `rgba(79,70,229,0.10)` | `rgba(59,130,246,0.12)` | Accent-tinted borders | | `--accent-focus` | `rgba(79,70,229,0.12)` | `rgba(59,130,246,0.15)` | Active states, selection bars | ### Semantic colors | Token | Light | Dark | Use for | |-------|-------|------|---------| | `--success` | `#16A34A` | `#22c55e` | Positive values, income, completed | | `--success-bg` | `#F0FDF4` | `rgba(34,197,94,0.1)` | Icon wells | | `--success-dim` | `rgba(34,197,94,0.08)` | `rgba(34,197,94,0.08)` | Badge backgrounds | | `--error` | `#DC2626` | `#ef4444` | Errors, issues, delete actions | | `--error-bg` | `#FEF2F2` | `rgba(239,68,68,0.1)` | Icon wells | | `--error-dim` | `rgba(239,68,68,0.08)` | `rgba(239,68,68,0.08)` | Badge backgrounds | | `--warning` | `#d97706` | `#f59e0b` | Warnings, pending states | | `--warning-bg` | `rgba(245,158,11,0.08)` | `rgba(245,158,11,0.1)` | Badge backgrounds | ### Overlay | Token | Light | Dark | Use for | |-------|-------|------|---------| | `--overlay` | `rgba(0,0,0,0.3)` | `rgba(0,0,0,0.6)` | Modal backdrop, reading pane overlay | | `--overlay-strong` | `rgba(0,0,0,0.5)` | `rgba(0,0,0,0.75)` | Heavy overlays | | `--nav-bg` | `rgba(255,255,255,0.9)` | `rgba(15,15,18,0.9)` | Navbar blur background | --- ## Typography | Token | Desktop | Mobile (≤768px) | Use for | |-------|---------|-----------------|---------| | `--text-xs` | 11px | 12px | Badges, pills, tiny counters | | `--text-sm` | 13px | 15px | Labels, meta, captions, button text | | `--text-base` | 14px | 16px | Body text, list items, inputs | | `--text-md` | 15px | 17px | Card titles, important rows (16px+ avoids iOS zoom) | | `--text-lg` | 17px | 18px | Section headers, modal titles | | `--text-xl` | 22px | 22px | Page titles | | `--text-2xl` | 28px | 26px | Hero headings | | `--text-3xl` | 36px | 32px | Large hero numbers | ### Line heights | Token | Value | Use for | |-------|-------|---------| | `--leading-tight` | 1.2 | Headings, hero numbers | | `--leading-snug` | 1.35 | Card titles, compact text | | `--leading-normal` | 1.5 | Body text | | `--leading-relaxed` | 1.65 | Article content | | `--leading-loose` | 1.8 | Long-form reading | ### Fonts | Token | Value | |-------|-------| | `--font` | `'Inter', -apple-system, system-ui, sans-serif` | | `--mono` | `'JetBrains Mono', ui-monospace, monospace` | --- ## Global Component Classes Defined in `app.css`, usable in any component without local `