Documentation
IntroductionArchitectureMonorepo & ToolingRunning Locally
Screen Flow MapUser JourneysPersonas & RolesWeb ScreensClient Portal
Convex OverviewData ModelFunctions ReferenceAuth & SessionsRoles & PermissionsJobs & Providers
Web Design SystemAdmin Console DesignPatterns & Conventions
Web AppiOS AppMarketing Website
Testing & HarnessCI Pipeline

Admin Console Design

Admin Console Design System

This console (apps/admin) is built on the Clinical theme — a single-theme, token-first design system intended as the template for future internal tools. Everything below is implemented in src/app/globals.css and src/components/.

The console itself: resizable grouped sidebar, sticky auto-titled page header, card surfaces, and the remapped blue accent
The console itself: resizable grouped sidebar, sticky auto-titled page header, card surfaces, and the remapped blue accent

Principles

  1. One theme, two appearances. Clinical is the only theme; light/dark via a .dark class on <html>, applied before first paint by an inline script reading localStorage.theme (default: system preference).
  2. The accent is a remapped ramp. Components always use Tailwind blue-* classes (bg-blue-600, text-blue-700, bg-blue-600/10). The theme remaps the entire blue ramp (and mirrors it to sky-* / indigo-*) to the brand color #3a6ea5 in OKLCH — so rebranding is a token edit, never a component edit.
  3. Severity stays stock. Red/amber/green ramps are never remapped and never used raw for status text — status goes through Badge variants and the --warning / --destructive tokens.
  4. No fake data, ever. If a surface has no real data source, it renders NotYetWiredBanner — never mock rows, mock KPIs, or placeholder charts.

Tokens

All tokens are CSS variables exposed to Tailwind via @theme inline (CSS-first, no tailwind.config.ts).

  • Neutrals (light): --background oklch(0.985 0 0), --foreground oklch(0.09 0 0), --card pure white, --muted-foreground oklch(0.45 0 0), --border oklch(0.92 0 0), --sidebar oklch(0.97 0 0).
  • Neutrals (dark): --background oklch(0.09 0 0), --card oklch(0.14 0 0), --sidebar oklch(0.07 0 0).
  • Accent: the blue ramp at hue 251.54 (brand #3a6ea5 ≈ blue-700 oklch(0.488 0.0953 251.54)); --primary, --ring, --chart-1, --sidebar-primary derive from it. Alert --destructive: #c4392c.
  • Radius scale: everything derives from --radius: 0.75rem — sm ×0.6, md ×0.8, lg ×1, xl ×1.4, 2xl ×1.8, 3xl ×2.2.
  • Type: sans = Hanken Grotesk, mono = IBM Plex Mono (next/font). Base html 16px, body 15px/1.625; h1 2.25rem/700/−0.025em, h2 1.5rem/700, h3 1.125rem/600. Everything antialiased; checkbox/radio accent-color: var(--primary).

Layout frame

  • AppFrame: flex h-screen min-h-0 = sidebar + main.flex-1.min-w-0.overflow-y-auto. Only main scrolls; scroll resets on navigation.
  • Sidebar: default 256px, drag-resizable 200–480 (double-click resets), collapsible to a 56px icon rail; width/collapsed/section state persist to localStorage. Titlebar (h-14) holds the app switcher (blue icon chip + name; popover when multiple apps are registered). Nav sections have sticky uppercase headers with rotating chevrons; the active route's section is force-expanded. Active links: bg-blue-600/10 text-blue-700 + aria-current="page"; matching is longest-prefix-wins. Footer: account popover (identity, role pill, appearance toggle, sign out) + collapse button.
  • Page headers: no global topbar. Each page renders a sticky PageHeader (h-14, bg-background/95 backdrop-blur) that auto-titles from the nav registry, with optional back link and actions slot. The h-14 intentionally aligns with the sidebar titlebar.
  • Content shell: max-w-7xl mx-auto px-8 py-6 space-y-6, cards as the base surface; data-dense screens may go full-bleed.
  • App registry (src/lib/apps.tsx) is the single source of truth: the sidebar, the /apps launcher, and page-header titles all derive from it.

Components

ComponentContract
Cardrounded-xl border bg-card shadow-sm; header p-6 space-y-1.5, content p-6 pt-0
Button (CVA)variants default (foreground-on-background), destructive, outline, secondary, ghost, link, success (bg-blue-600); sizes h-9/h-8/h-10/icon; asChild via Radix Slot; focus ring-ring/50 ring-[3px]
Badge (CVA)rounded-md px-2 py-0.5 text-xs font-medium; default, secondary, destructive, success (blue), warning, outline
Tabscustom (not Radix), roving tabindex. underline: active -mb-px border-b-2 border-blue-600; pill: bg-muted p-1 rounded-lg, active bg-background shadow-sm. The active underline always renders, even with one tab. UnderlineTabs is URL-query-param-driven with count pills
StatTileuppercase 12px label over text-2xl tabular-nums value; with onClick becomes a KPI-as-filter toggle (border-blue-600 bg-accent/30 active). StatTileRow keeps tiles to one measured row via ResizeObserver
Tablesbordered cards: overflow-hidden rounded-xl border bg-card + divide-y; numeric cells tabular-nums; header cells text-xs uppercase tracking-wide text-muted-foreground
Skeletonsrounded bg-muted bars in animate-pulse containers with aria-busy; shape-matched per layout and used in every route's loading.tsx so swaps don't reflow
Popovershand-rolled, portaled to <body> with fixed positioning; outside-pointer + Escape dismiss; shell rounded-lg border bg-popover shadow-lg p-1. No native <select> — SelectMenu / MultiSelect build on this pattern
NotYetWiredBannerdashed rounded-lg border-dashed bg-muted/40 with a PlugZap icon — the mandated empty state for unwired surfaces
Docs viewer/docs/[slug] renders markdown via react-markdown + remark-gfm with the console's typographic renderers

The flow map

The Screen Flow Map is a custom SVG canvas (no diagram library): swim-lane bands, hand-positioned columns, cubic-bezier edges with per-kind styling (workflow / redirect / sidebar), wheel zoom toward cursor, drag pan, fit/100% controls, search, a minimap, and a selection panel listing inbound/outbound navigation. Its data (src/lib/flowData.ts) is derived from the product's actual route tree and navigation calls.

The flow map canvas — swim lanes by persona, edge-kind filters, minimap, and the node detail panel
The flow map canvas — swim lanes by persona, edge-kind filters, minimap, and the node detail panel

Docs diagrams

The documentation's own diagrams are theme-aware React SVG components (src/components/diagrams/), not images: boxes render HTML in foreignObject so text wraps and inherits tokens, which is why every diagram adapts to light and dark automatically. Markdown embeds one with a fenced code block whose language is diagram and whose body is the registry name (see diagrams/index.tsx). Screenshots in the docs live in public/screenshots/<theme>/<mode>/ and follow the console's live appearance the same way; they are captured from the running seeded app by node tools/assets/assets.mjs docs — re-run it whenever the product UI changes materially.