Design System
Status: v1.0 · Owner: Design Lead + Frontend Lead · Gate input: G0, G2, G5 The visual and component contract for Atlas. Tokens here are the canonical source; Tailwind config and Figma both mirror them. This implements the "design system seed" in
Phase_0.md§5.4 and the theme model inPhase_5.md§5.4. No raw colour values may appear in component code — only token references (lint-enforced).
1. Foundations
1.1 Design language
Modern, calm, data-dense but not cluttered. Reference points: Linear (restraint, motion), Vercel/Geist (typographic clarity), ThingsBoard (dashboard density) — but quieter and more accessible. The UI recedes; the data is the hero.
- Surface-led, not border-led. Hierarchy comes from elevation and subtle background steps, not heavy 1px borders everywhere.
- Generous whitespace at the chrome, dense at the data. Lists and tables can be compact; navigation and headers breathe.
- Restraint with colour. Neutral-dominant UI; colour reserved for brand, semantics, and data.
1.2 Theming tiers
Three cascading tiers (per Phase_5.md §5.4):
- Base —
light/dark. The product's own palette. - Brand (tenant) —
tenants.brandingoverrides--primary,--accent, logo, font, radius, density. Applies in < 1 frame on load. - User overrides — accent + density (
cosy/comfortable/compact), persisted on the user record.
All three are expressed as CSS custom properties on :root / .dark / [data-brand]. Switching a theme reassigns variables — no re-render of colour-hardcoded components because there are none.
2. Color tokens
Tokens are semantic, not literal (--primary, not --blue-600). Components reference semantic tokens only. A raw palette feeds the semantic layer.
2.1 Semantic token set (canonical names)
:root {
/* Surfaces (elevation steps) */
--background: /* app canvas */ ;
--surface: /* cards, panels */ ;
--surface-raised: /* popovers, menus */ ;
--surface-sunken: /* wells, code, insets */ ;
--overlay: /* modal scrim */ ;
/* Content (text/icon on surfaces) */
--foreground: /* primary text */ ;
--foreground-muted: /* secondary text */ ;
--foreground-subtle:/* tertiary / placeholder */ ;
/* Lines */
--border: /* default hairline */ ;
--border-strong: /* emphasized divider */ ;
--ring: /* focus ring */ ;
/* Brand (overridable by tenant) */
--primary: /* brand action */ ;
--primary-foreground: ;
--accent: /* secondary brand */ ;
--accent-foreground: ;
/* Semantic / status (paired with icon + label, never colour alone) */
--success: ; --success-foreground: ;
--warning: ; --warning-foreground: ;
--danger: ; --danger-foreground: ;
--info: ; --info-foreground: ;
/* Data-viz categorical (chart series, colour-blind-safe order) */
--chart-1: ; --chart-2: ; --chart-3: ; --chart-4: ;
--chart-5: ; --chart-6: ; --chart-7: ; --chart-8: ;
}
2.2 Reference palette (default base — light)
These seed values are the Phase 0 placeholder palette; the designer refines brand tokens in Phase 5. Chosen for WCAG-AA contrast on their paired foregrounds.
| Token | Light | Dark | Notes |
|---|---|---|---|
--background | #F7F8FA | #0B0D12 | app canvas |
--surface | #FFFFFF | #12151C | cards |
--surface-raised | #FFFFFF | #1A1F29 | menus/popovers |
--surface-sunken | #EEF0F4 | #080A0E | wells |
--foreground | #0E1116 | #E6E9EF | ≥ 12:1 on background |
--foreground-muted | #56607A | #9AA4B8 | ≥ 4.5:1 |
--foreground-subtle | #8A93A6 | #6B7488 | placeholders ≥ 3:1 (non-essential) |
--border | #E3E6EC | #232936 | hairline |
--ring | #1F2A44 | #7CA2FF | focus, ≥ 3:1 vs adjacent |
--primary | #1F2A44 | #5B7CFF | brand (tenant overrides) |
--accent | #0EA5A5 | #22C1C1 | secondary brand |
--success | #1F9D57 | #34D17F | with check icon + label |
--warning | #B7791F | #E0A33E | with ⚠ icon + label |
--danger | #C0392B | #F2685A | with × icon + label |
--info | #2563C9 | #5B92F0 | with ⓘ icon + label |
All seed values are Phase 0 placeholders; the designer finalises the brand-token tier in Phase 5. Each must hold its stated contrast ratio against its paired surface (axe-checked at the G2/G5 gates).
Data-viz palette is ordered for colour-vision-deficiency safety (Okabe–Ito-derived): blue, orange, teal, magenta, green, amber, purple, grey. Series also differ by shape/dash where lines overlap, so colour is never the only differentiator.
2.3 Contrast rules (gate condition, axe-checked)
- Body text & essential icons: ≥ 4.5:1.
- Large text (≥ 24px or 19px bold) & UI graphics/borders: ≥ 3:1.
- Focus ring vs adjacent colours: ≥ 3:1.
- Status is always icon + text + colour. Removing colour must not remove meaning.
3. Typography
- Type family:
Geist(orInter) for UI;Geist Mono/JetBrains Monofor code, IDs, telemetry values. Japanese fallback:Noto Sans JP(layouts tolerate longer strings — no critical truncation). - Scale (4-pt rhythm, rem):
| Token | Size / line-height | Use |
|---|---|---|
text-display | 36 / 40 | TV-mode hero metric |
text-h1 | 28 / 34 | page title |
text-h2 | 22 / 28 | section |
text-h3 | 18 / 24 | card title |
text-body | 14 / 20 | default body |
text-sm | 13 / 18 | secondary / table |
text-xs | 12 / 16 | meta, captions |
metric-lg | 44 / 48 | KPI headline number |
metric-md | 30 / 34 | KPI card number |
- KPI numbers use tabular-nums so digits don't jitter on live update.
- Max line length for prose (AI answers, docs): ~72ch.
4. Spacing, radius, elevation, density
4.1 Spacing — 4pt grid
0, 2, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64. Components use scale steps, never arbitrary px.
4.2 Radius (tenant-overridable via --radius)
--radius-sm 6 · --radius-md 10 · --radius-lg 14 · --radius-full 9999. Default control radius = md.
4.3 Elevation (shadow tokens — dark mode uses lighter borders + glow, not heavy shadow)
--shadow-sm (hairline rest), --shadow-md (cards on hover, dropdowns), --shadow-lg (modals, popovers), --shadow-overlay (sheets).
4.4 Density (user override)
| Density | Row height | Control height | Base padding |
|---|---|---|---|
cosy | 48 | 44 | 16 |
comfortable (default) | 40 | 36 | 12 |
compact | 32 | 28 | 8 |
Density is a data-density attribute on <html>; control sizes read from density-aware tokens. Mobile forces ≥ comfortable so touch targets never drop below 44px regardless of user setting.
5. Iconography & imagery
- Icon set: Lucide (consistent 1.5px stroke, 24px grid). One set only.
- Icons paired with text for any action whose meaning isn't universal. Status icons are mandatory companions to status colour.
- Empty-state illustrations: a small, consistent, theme-tinted set (line style), used on empty states (
UX_patterns.md). - Brand logo: rendered from
tenants.branding.logo; always has a text fallback for accessibility and TV legibility.
6. Motion
Motion communicates causality and continuity; it is never decorative-only and always respects prefers-reduced-motion.
| Token | Duration | Easing | Use |
|---|---|---|---|
motion-instant | 80ms | ease-out | hovers, toggles |
motion-fast | 150ms | ease-out | dropdowns, tooltips, theme switch |
motion-base | 220ms | ease-in-out | drawers, sheets, tab change |
motion-slow | 320ms | spring-soft | modal in, page transition |
motion-tv | ≤ 250ms | ease-in-out | TV-mode panel rotation (Phase_5.md §6.3) |
Rules: optimistic UI animates immediately; loading uses skeletons not spinners where layout is known; prefers-reduced-motion: reduce → all non-essential motion becomes an instant opacity change.
7. Component library (base set — Phase 0/2)
Built on shadcn/ui + Radix primitives, restyled to tokens. Each ships with a Storybook story covering all states × light/dark/brand.
Primitives: Button (primary/secondary/ghost/destructive/icon, + loading/disabled), Input, Textarea, Select, Combobox, Checkbox, Radio, Switch, Slider, DatePicker, TimeRange picker.
Containers: Card, Panel, Sheet/Drawer, Modal/Dialog, Tabs, Accordion, Popover, Tooltip, HoverCard.
Feedback: Toast, Banner/Alert, Badge/Chip, Tag, ProgressBar, Spinner, Skeleton, EmptyState, ErrorState.
Data: DataTable (sort/filter/select/paginate/column-pick/saved-views), DefinitionList, Stat/KPI card, Timeline, Avatar, Pagination.
Navigation: Sidebar, BottomNav, TopBar, Breadcrumbs, CommandPalette (⌘K), ContextMenu, Pagination, Stepper.
Forms: FormField (label/help/error), all generated from Zod schema → React Hook Form (Phase_5.md §6.4) so dashboard/workflow inspectors are consistent with hand-built forms.
7.1 Component state requirements (every interactive component)
Default · Hover · Focus-visible (ring) · Active/Pressed · Disabled · Loading · Error · Read-only/No-permission. A component is "done" only when its Storybook story shows all of these.
8. Domain components (Phase 4/5/6/7)
Reusable, token-driven, used across modules and dashboard widgets:
- KPICard — label, big tabular metric, delta + trend sparkline, drill target, threshold colour (icon+colour).
- StatusPill — work-order/workflow status; icon + label + colour; consistent state→colour map.
- PriorityTag — P1–P4 with shape+colour.
- SLAIndicator — countdown / breached; turns danger with icon, not colour alone.
- TelemetryChart — line/area, live-binding aware, theme-tokened series, tabular tooltip.
- Gauge — radial, threshold bands.
- AssetMarker — on ceiling-plan overlay; clickable; hover summary.
- AlertFeedItem — severity, source, time, drill-through; high-contrast for War Room.
- AIMessage — markdown answer + citations chips + tool-transparency disclosure + feedback thumbs.
- WorkflowNode / Edge — React Flow node skins per category, with lint/error badges.
- ImportPreviewTable — column mapping, row validation, error highlighting.
Each domain component declares which view modes it supports (standard | tv | war-room) per the widget manifest (Phase_5.md §5.1).
9. Dashboard-widget visual catalogue (Phase 5)
The 24 launch widgets (Phase_5.md §5.7) compose from the domain components above. Visual rules:
- Every widget renders in light/dark/brand with no hardcoded colour.
- Every widget has loading + empty + error + no-data-in-range states.
- Live widgets (SSE) show a subtle "live" pulse and a last-updated time.
- A widget value is clickable to its canonical record (universal drill-through).
- Each widget has a Storybook story with sample data per theme → feeds visual-regression gate.
10. Governance
- Token lint: CI fails on raw
#hex/rgb()/hsl()literals outsidetokens.css/tailwind.config.ts(Phase_5.mdrisk mitigation). - Storybook = contract: no component merges without a story showing all states in all themes.
- Visual regression: Chromatic/Playwright snapshots per component × theme are a required PR check.
- A11y in Storybook:
@storybook/addon-a11yruns on every story; axe-clean is required. - Versioning: the design system is versioned; breaking token changes require a design + frontend review and a changelog entry.