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.
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 <style> duplication.
| Class |
Description |
.module |
Card container (bg, border, shadow, padding) |
.module.primary |
Hero card (more padding + elevation) |
.module.flush |
No padding (for flush content) |
.module-header |
Flex header row (title left, action right) |
.module-title |
Uppercase label |
.module-action |
Accent link → "View all →" |
.data-row |
Standard list item with hover + zebra striping |
.badge + .error/.success/.warning/.accent/.muted |
Semantic status badges |
.tab-bar + .tab + .tab-badge |
Pill-style tab navigation |
.section-label |
Uppercase group header |
.btn-primary / .btn-primary.full |
Primary action buttons |
.btn-secondary |
Secondary action buttons |
.btn-icon |
Square icon button (36×36) |
.input |
Standard text input |
.skeleton |
Shimmer loading placeholder |
.page / .page-header / .page-title / .page-greeting |
Page-level layout |
.app-surface |
Centered max-width container |
Intentional Raw Values
These values exist outside the token system by design. Do not convert them.
Non-scale spacing
Values that don't land on the 4px grid. Used for optical tuning where grid steps are too coarse.
| Value |
Where |
Why |
1px |
margin-top on meta text, feed separators |
Sub-pixel nudge for vertical alignment |
3px |
Badge padding-y, kbd padding |
Optical centering within small elements |
5px |
Pill padding-y, chip padding |
Between sp-1 (4px) and sp-1.5 (6px), tuned per element |
7px |
View-btn padding-y, sidebar gap |
Between sp-1.5 (6px) and sp-2 (8px) |
9px |
Nav item padding-y, suggestion row padding-y |
Between sp-2 (8px) and 10px |
10px |
Input padding-y, dropdown padding, various gaps |
Common "comfortable touch" size, between sp-2 and sp-3 |
11px |
Entry row padding-y, toggle btn padding-bottom |
Asymmetric optical alignment |
13px |
FAB action padding-y, list row padding |
Between sp-3 (12px) and 14px |
14px |
Button padding-x, row padding-x, modal gap, field row gap |
Most common off-grid value. Used as standard horizontal rhythm for interactive elements. Also --row-pad-y for data rows. |
15px |
Transaction row padding-y |
Between sp-3.5 and sp-4, tuned for readability |
18px |
Detail header margin-bottom, AI guide padding-top |
Between sp-4 (16px) and sp-5 (20px) |
22px |
Trip stats padding-x, modal body padding |
Between sp-5 (20px) and sp-6 (24px) |
Non-scale border-radius
| Value |
Where |
Why |
9px |
Status segment control inner radius |
Between radius-md (8px) and radius (12px) |
10px |
Event cards, photo thumbnails, notes, modals, unscheduled items, food rows |
Heavily used "soft card" radius. Between radius-md (8px) and radius (12px). |
14px |
CommandPalette box, toggle track |
Large interactive controls |
20px |
Bottom sheet top corners |
Extra-round for sheet feel |
50% |
Circular elements (cover nav dots, meal-number, image-delete) |
True circle, differs from radius-full on non-square elements |
Data visualization colors
These are visual category identifiers, not semantic UI colors. They must remain consistent within their visualization set, independent of theme.
Fitness macros:
| Color |
Hex |
Use |
| Protein |
#8B5CF6 |
Purple macro bar |
| Carbs |
#F59E0B |
Amber macro bar |
| Fat |
#3B82F6 |
Blue macro bar |
Fitness meal weights:
| Color |
Background |
Text |
Meaning |
| Heavy |
rgba(239,68,68,0.1) |
#DC2626 |
High-calorie meal |
| Moderate |
rgba(245,158,11,0.1) |
#B45309 |
Medium-calorie meal |
| Light |
rgba(34,197,94,0.1) |
#15803D |
Low-calorie meal |
Trip categories:
| Color |
Background |
Text |
Category |
| Hotel |
rgba(168,85,247,0.1) |
#a855f7 |
Lodging |
| Restaurant |
rgba(249,115,22,0.1) |
#f97316 |
Food & dining |
| Hike |
rgba(34,197,94,0.15) |
#16a34a |
Outdoor activity |
| Logistics |
rgba(161,161,170,0.1) |
--text-3 |
Transport, other |
Other:
| Color |
Hex |
Use |
| Favorite star |
#F59E0B |
Star icon fill (reader, fitness) |
| AI badge |
rgba(59,130,246,0.1) / #3B82F6 |
AI-logged entry indicator |
| Transfer pill |
rgba(59,130,246,0.1) / #3b82f6 |
Budget transfer indicator |
Glass & overlay effects
Applied to elements layered over images. Not theme-switchable because they depend on photo content, not UI surface.
| Value |
Where |
rgba(0,0,0,0.7)..0.1 |
Cover image gradient (trips) |
rgba(255,255,255,0.15) |
Cover nav button background |
rgba(255,255,255,0.3) |
Cover nav button hover |
rgba(0,0,0,0.3) / rgba(0,0,0,0.5) |
Cover share button / hover |
rgba(0,0,0,0.35) |
Modal overlays (trips), detail overlay (inventory) |
rgba(0,0,0,0.5) |
Image delete button, search saving overlay |
Directional panel shadows
Non-standard shadow directions for slide-in panels and bottom sheets. Cannot use elevation tokens.
| Value |
Where |
0 -8px 32px rgba(0,0,0,0.12) |
FAB bottom sheet (fitness, trips) |
-12px 0 40px rgba(0,0,0,0.12) |
Detail slide-in sheet (inventory) |
-6px 0 28px rgba(0,0,0,0.08) |
Reading pane (reader) |
-8px 0 32px rgba(0,0,0,0.1) |
Edit sheet (trips) |
8px 0 24px rgba(0,0,0,0.08) |
Mobile sidebar (reader) |
Accent-tinted FAB shadows
Colored shadows for floating action buttons. Not in the elevation scale because they use accent color, not black.
| Value |
Where |
0 8px 24px rgba(79,70,229,0.3) |
FAB resting (fitness) |
0 12px 32px rgba(79,70,229,0.4) |
FAB hover (fitness) |
0 6px 20px rgba(79,70,229,0.3) |
FAB resting (trips) |
0 8px 28px rgba(79,70,229,0.4) |
FAB hover (trips) |
Near-match shadows
Shadows that are close to tokens but intentionally differ in blur radius or opacity.
| Value |
Nearest token |
Diff |
0 2px 6px rgba(0,0,0,0.05) |
--shadow-xs |
Larger blur, higher opacity |
0 1px 2px rgba(0,0,0,0.04) |
--shadow-xs |
Opacity 0.04 vs 0.03 |
0 1px 4px rgba(0,0,0,0.04) |
--shadow-xs |
Larger blur |
0 1px 4px rgba(0,0,0,0.08) |
--shadow-xs |
Larger blur + opacity |
0 16px 48px rgba(0,0,0,0.15) |
--shadow-xl |
Single layer vs dual |
0 1px 3px rgba(0,0,0,0.06) |
--shadow-xs |
Higher opacity |
0 8px 24px rgba(0,0,0,0.12) |
--shadow-md |
Higher opacity |
0 20px 60px rgba(0,0,0,0.15) |
--shadow-xl |
Single layer |
0 20px 60px rgba(0,0,0,0.2) |
--shadow-xl |
Single layer, higher opacity |
0 1px 3px rgba(0,0,0,0.15) |
--shadow-sm |
Toggle thumb, much higher opacity |