Style guide
The living reference for this console. Every example below is static — nothing here fetches data. If a pattern is not on this page, it is not part of the system yet.
Color
Four color languages
- Dispatchability —
gov-*, only on a labelled lane chip, a map marker or a lane-distribution chart. - UI state / severity —
signal-*, five steps: critical → warning → caution → positive, plus neutral and info. - Health tier —
health, neutral slate, chips only. Never traffic-light colored. - Brand / intent —
primary(brand, nav-active, links),action(CTAs),destructive(destructive actions only).
A filled labelled chip always means a governance lane. Colored text always means a UI state. The two vocabularies are never mapped onto each other.
Surfaces
Brand and intent
Governance lanes
Health tier (Critical / Degraded / Healthy)
Signal (UI state)
Confidence
Charts
Typography
Type roles
Spacing and radius
Elevation
Icons
Components
Nested panel
Do and don't
Palette colors bypass theming. Use a semantic token.
A lane is a labelled chip. A colored card edge is not readable as a lane.
Two named steps replace eight invented micro sizes.
One formatting layer keeps every number comparable.
A blank cell is ambiguous; N/A is an honest absence.
A fabricated 0 reads as a measurement. It never is.
Column headers carry the label once, not on every row.
Numbers align right in a monospaced metric face; text aligns left.
Keep known data on screen and dim it; never replace it with a spinner.
Card content is top-aligned so scanning down a grid works.
Chips, meters and filters
One chip, one delta, one bar, one segmented control, one filter tile. A page never declares its own — ESLint blocks locally-named *Chip, *Pill, *Tag and *Bar components outside components/shared.
A filled labelled chip means a governance lane. Everything else is an outlined Chip with a signal tone.
Status vocabulary
Four axes, four looks. A tag never borrows another axis' colour, and colour is never the only carrier of meaning — every tag is labelled.
gov-* is reserved for the governance lane axis. Severity uses signal-*, health uses the health token, and interaction uses brand/action. The design-system audit enforces this: any gov-* colour outside the lane-owning components fails the check.
Filled gov-* colour. Only lane components paint with governance colour.
Outlined signal tones from tone.ts. There is no informational tone: informational state is neutral.
Workflow position, not severity — neutral everywhere except Paused, which is a caution. One map, in badges.tsx.
A decision is a signal-toned tag; a call to action is a real button or plain text — never a tag that looks clickable but is not.
Exactly two sizes, identical padding across every badge. There is no md, and tags never carry a fixed width.
Maps and charts
One map mount. Every map surface renders through ConsoleMap. Pages never call the Maps API directly, so a pin means the same thing everywhere: icons come from the shared marker vocabulary, detail bubbles are React (never HTML strings), pin size is constant, and records without usable coordinates are filtered out and counted in a visible note. Heights use sm | md | lg | fill.
One chart frame. Every recharts consumer renders through ChartFrame: series colors from the chart tokens above, a required accessible name, a stated unit, one legend implementation, and shared loading / error / empty / stale states. An empty series shows a no-data state, never blank axes — blank axes read as zero, which is a different claim.
Reference layouts
When to use which: Dashboard is the landing view for a role or scope — a KPI strip plus a widget grid, nothing more. Detail page is for one entity (a node, a facility, a dispatch) — breadcrumbs, its own KPI strip, and a primary/sidebar split. Table page is for browsing or triaging a collection — toolbar, DataTable, pagination. Map page is for spatial triage — a full-bleed map with an overlay legend and mode toggle. Each archetype below is static and non-fetching; copy the shape, not the literal data.
Widget heights are never hand-set. They derive from the widget’s declared size — the grid column span and row span come from the same table the widget registry reads:
Network overview
Role-scoped landing view.
Attention queue
Lane distribution
Top facilities
Top nodes
Trajectories
Provenance
Node ND-1284
Fault history
Metadata
Provenance
Nodes
Filter bar, sortable table, pagination.
Node | Facility | Lane | |
|---|---|---|---|
| ND-1284 | FAC-0091 | Amber | 4 |
| ND-1301 | FAC-0044 | Green | 12 |
| ND-1417 | FAC-0091 | Red | 1 |
| ND-1502 | FAC-0210 | Clear | 0 |
- NodeND-1284FacilityFAC-0091LaneAmberFaults4
- NodeND-1301FacilityFAC-0044LaneGreenFaults12
- NodeND-1417FacilityFAC-0091LaneRedFaults1
- NodeND-1502FacilityFAC-0210LaneClearFaults0
Node map
Full-bleed map, mode toggle, legend overlay.