Browse documents

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 in Phase_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):

  1. Baselight / dark. The product's own palette.
  2. Brand (tenant)tenants.branding overrides --primary, --accent, logo, font, radius, density. Applies in < 1 frame on load.
  3. 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.

TokenLightDarkNotes
--background#F7F8FA#0B0D12app canvas
--surface#FFFFFF#12151Ccards
--surface-raised#FFFFFF#1A1F29menus/popovers
--surface-sunken#EEF0F4#080A0Ewells
--foreground#0E1116#E6E9EF≥ 12:1 on background
--foreground-muted#56607A#9AA4B8≥ 4.5:1
--foreground-subtle#8A93A6#6B7488placeholders ≥ 3:1 (non-essential)
--border#E3E6EC#232936hairline
--ring#1F2A44#7CA2FFfocus, ≥ 3:1 vs adjacent
--primary#1F2A44#5B7CFFbrand (tenant overrides)
--accent#0EA5A5#22C1C1secondary brand
--success#1F9D57#34D17Fwith check icon + label
--warning#B7791F#E0A33Ewith ⚠ icon + label
--danger#C0392B#F2685Awith × icon + label
--info#2563C9#5B92F0with ⓘ 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 (or Inter) for UI; Geist Mono / JetBrains Mono for code, IDs, telemetry values. Japanese fallback: Noto Sans JP (layouts tolerate longer strings — no critical truncation).
  • Scale (4-pt rhythm, rem):
TokenSize / line-heightUse
text-display36 / 40TV-mode hero metric
text-h128 / 34page title
text-h222 / 28section
text-h318 / 24card title
text-body14 / 20default body
text-sm13 / 18secondary / table
text-xs12 / 16meta, captions
metric-lg44 / 48KPI headline number
metric-md30 / 34KPI 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)

DensityRow heightControl heightBase padding
cosy484416
comfortable (default)403612
compact32288

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.

TokenDurationEasingUse
motion-instant80msease-outhovers, toggles
motion-fast150msease-outdropdowns, tooltips, theme switch
motion-base220msease-in-outdrawers, sheets, tab change
motion-slow320msspring-softmodal in, page transition
motion-tv≤ 250msease-in-outTV-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 outside tokens.css / tailwind.config.ts (Phase_5.md risk 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-a11y runs 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.