Dashboards
1 rule checked before an AI-produced dashboard design ships.
Dashboards — Design Taste & Build Scope (v2.5)
Paste this alongside every dashboard build prompt as the governing standard. It owns all the shared design, layout, scope, session, and AI rules so the per-dashboard prompt only has to specify what's unique to that product. When anything here conflicts with a per-dashboard prompt, this document wins unless the per-dashboard prompt explicitly overrides a named rule.
★ Core principle (the #1 rule)
Hierarchy comes from type size + a strict grey ink ladder — not from colour. Colour is reserved for brand accents and status only. A dense data layer should read clearly when it's essentially one faint grey at one small size; structure and importance come from the ink ladder (§5), type scale (§6), spacing, and grouping. If you're reaching for a colour to signal emphasis, stop — use weight, size, or a darker ink instead.
Practically this means: no colour-coding cells for emphasis, no filled colour chips scattered for attention, no icon tiles decorating every KPI. One accent moment per screen, at most.
1. Session & scope model
- Size the session to the page budget. A lean dashboard at the page budget below usually fits in well under half of a ~5-hour Claude Code session; two such dashboards is the common case. If a specific product's scope runs heavier than usual, one dashboard per session is fine; don't compress quality to force a second one in.
- Twelve key pages per dashboard — with a deliberate composition. Build only the top 12 pages that best present the product on a portfolio, and spread them across presentation formats so the set never reads as all-tables-and-charts:
- 1 — Main dashboard (overview).
- 2 — Analytics page (chart-led).
- 3–5 — Three table-led pages (list / management screens on the global
.data-grid). - 6 — One detail / profile page — a drill-down into a single record (e.g. one vendor, one client, one transaction), pairing naturally with one of the table-led pages above.
- 7–12 — Six pages that present data without tables or charts — using other formats: card grids, kanban, timelines / feeds, comparison columns, config / routing panels, progress/budget panels. Among these six, include two forms — create / add flows such as new user, client, patient, service, provider, or budget.
- The aim is to show the dashboard's depth and variety of surfaces without building everything in full.
- Show full depth, build a slice. Render the complete section/navigation list (all groups and items) so the dashboard's true scope is visible, but only build the first one or two screens per section group until the 12-page budget is spent. Unbuilt nav items are visible and can route to a tasteful "section preview" placeholder — the point is to show breadth without building it all.
- Two user categories only. Build the views for exactly Product Admin and Lead Developer. All other roles (e.g. Super Admin, Product Developer) still appear by name in the role-switch popup so the role model reads as complete — they just aren't separately built.
- Minimal, senior code. Just enough structure to be clean, on-brand, and convincing. No premature abstraction, no scalability scaffolding.
2. Roles, switching & navigation
- Role switch lives on the Settings page, not in the top bar.
- The switch popup lists every role by name; only Product Admin and Lead Developer resolve to built views.
- No preview banner. Do not render any "Previewing the console as X — data and actions are scoped to this role" banner anywhere. The switch changes the view silently.
- Keep real-time role switching (the view updates without a page reload) — it's the core interaction, just triggered from Settings.
3. Layout system
- Header height = logo-area height. Define one header-height token (≈ 56px / h-14). The logo lockup area (top-left, above the sidebar) and the top bar are the exact same height, with aligned bottom edges. Header is sticky,
border-bhairline. - Content top offset ≈ 50px — the main dashboard is fully exempt. On every screen other than the main dashboard/overview page, leave ~50px of deliberate vertical space between the top bar and the first page content (title/count/first block); this gap supersedes any smaller default content padding. The main dashboard page does not follow this rule at all — it just uses ordinary page padding (~20–28px), the same spacing it would have if no special top-offset treatment existed.
- Sidebar: ≈ 210px wide, surface/white,
border-rhairline. Nav itemspx-3 py-2.5rounded-lg at 11.5px; active = primary-tint background + primary text + semibold; inactive nav text/section links =ink-soft(gray-400) — light, but still clearly legible against the sidebar surface — matching the inactive icon colour, so an inactive row (icon + text) reads as one consistent tone; group labels 10px uppercase; a user card in the footer. Long nav labels truncate to a single line (ellipsis,white-space: nowrap; overflow: hidden) — never wrap to two lines. - Independent sidebar scroll. The sidebar scrolls on its own track; when main content is taller, the sidebar stays put and does not scroll with it (sticky / own-overflow).
- Main content: horizontal padding
px-8–10,max-w-[1360px]. After the top offset (§3), vertical rhythm isspace-y-5; card paddingp-4–6; KPI gridgap-3–4. - Center underfilled pages. When content doesn't fill the viewport (forms, empty-ish detail pages, single-card screens), center the content area both vertically and horizontally — never top-left aligned in a sea of space.
- Visual hierarchy on dense pages. Use size, weight, the ink ladder, spacing, and grouping to establish clear primary/secondary/tertiary reading order. Dense ≠ flat.
4. Top bar — rotate across four variants
Assign a different variant per dashboard, cycling v1 → v2 → v3 → v4 so the portfolio shows range.
- v1 — Split: search bar at the far left; date, notifications, and profile grouped at the far right; open space between them.
- v2 — Greeting: "Good Morning / Afternoon / Evening, {name}" at the far left; search bar centered; a date-and-time chip at the far right.
- v3 — Minimal split: search bar at the far left; a date chip at the far right; nothing else.
- v4 — Centered search: a single search bar centered in the top bar; nothing else.
5. Colour system
Colour is rationed (see Core principle). Discipline:
- Primary appears only on: active nav, primary buttons, key numbers / table total count, links, and the primary chart series.
- Accent is rarer still: CTAs, one active-state, and at most one hero moment per screen.
- Everything else is greyscale — structure via the ink ladder and hairlines.
Neutral grays — cool / bluish (never reddish or brown)
Surfaces and structure. Scale 50–900:
| Level | Hex |
|---|---|
| 50 | #F8FAFC |
| 100 | #F1F5F9 |
| 200 | #E2E8F0 |
| 300 | #CBD5E1 |
| 400 | #94A3B8 |
| 500 | #64748B |
| 600 | #475569 |
| 700 | #334155 |
| 800 | #1E293B |
| 900 | #0F172A |
Shift the hue slightly per brand if needed, but keep it cool / blue-leaning.
The grey ink ladder (most important adjustment)
Text hierarchy runs on theme-aware ink tokens (bluish, dark-mode-safe), not on ad-hoc greys. Define once and use everywhere:
--ink-em: 222 24% 16%; /* .ink-em values, names, page titles, big numbers (~gray-900) */
--ink-strong: 218 17% 30%; /* .ink-strong headings, strong/amount cells (~gray-700) */
--ink: 218 12% 42%; /* .ink standard body (~gray-600) */
--ink-muted: 220 9% 54%; /* .ink-muted labels, secondary, meta (~gray-500) */
--ink-soft: 220 11% 66%; /* .ink-soft default table cells, meta, icons (~gray-400) */
--ink-faint: 220 14% 80%; /* .ink-faint S/N, placeholders (~gray-300) */
Usage: default table cell = ink-soft; table main column (name/title) = ink-strong (gray-700) at 11px — 1px above the 10px base; secondary IDs/refs = ink; amounts = ink-strong + semibold; serials/placeholders = ink-faint (gray-300); inactive sidebar nav text (and its icon) = ink-soft (gray-400); big stat numbers & page titles = ink-em.
Dividers & borders — hairlines only
Never heavy. Three tokens:
--border— control borders (inputs, selects, buttons).--hairline(≈ gray-100) — section dividers, card borders, thead underline.--hairline-soft(≈ gray-50) — table row dividers.
Main colour — generate levels 25–900
For each dashboard's primary brand colour, generate a full ramp: 25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 (25 = barely-tinted near-white; 500 = base brand; 900 = deepest).
Usage map:
- 25 / 50 — content backgrounds, hover surfaces, faint fills, primary-tint for active nav / badges
- 100 / 200 — subtle fills, chart bar-hover tint (§8)
- 300 / 400 — borders/dividers when brand-tinted, secondary accents
- 500 — base brand, primary actions, table total count, active nav, primary chart series
- 600 / 700 — hover/active on primary;
primary-darkfor text-on-tint (e.g. page chip) - 800 / 900 — deep accent, brand colour used as type
Backgrounds
Main content-area backgrounds use the lightest level — neutral-50 or main-25 — never pure white and never a strong tint. Cards sit brighter (white/surface) against it.
Semantic colours
Success / warning / danger use the tint bg / mid text / faint border recipe, kept visually distinct from both the brand colour and the accent, so status is never ambiguous.
6. Typography
- Base font size: 9–10px. Use 10px as the default body/data size; 9px for the smallest labels. Everything scales from there. Weight and size — not colour — are the primary hierarchy tools.
| Token | Size / style | Use |
|---|---|---|
| .type-display | 22–24px bold, ink-em | stat / big numbers |
| total count | 16–18px semibold, primary | count above a table |
| .type-heading | 18px semibold, ink-em | page title (in the main content area) |
| .type-subtitle | 12px, ink-muted | page subtitle, directly under the title |
| .type-subheading | 13px semibold | card title |
| nav items | 11.5px | sidebar (inactive = ink-soft) |
| .type-label | 11px | KPI / field labels |
| meta / pagination | 11px, ink-muted | secondary text |
| .type-eyebrow | 10px uppercase, 0.06em, ink-soft | section / column labels |
| data / table cells | 10px, ink-soft | the base |
| table main column | 11px, ink-strong (gray-700) | name/title cell — 1px above the base |
- Use a monospace for IDs, refs, counts, and prices so data reads as precise.
- The per-dashboard prompt names the actual typeface(s).
7. Surfaces, radius, shadow
- Cards: white /
surface,border-hairline, very light shadow only (0 1px 2px rgb(15 23 42 / 0.04)) — never heavy. - Radius: base container
--radius: 0.7remfor section/container cards; metric / stat cards capped at 4–8px (rounded-lg); controlsrounded-md(~6px); badgesrounded-full. - No coloured side or bottom border/outline on any card, anywhere. Neutral hairline and/or the soft shadow only.
8. Components
StatCard / KPI (kept deliberately plain)
- No icon tiles. No filled colour delta chips.
- Composition:
.type-label+ a bigink-emnumber + one restrained delta line (small green/red arrow when it carries meaning, otherwise muted). - Tile:
rounded-lg(≤8px) ·border-hairline·bg-card·p-4. - Grid balancing — no row may have more than one empty column. Choose cards-per-row so the last row is at most one slot short; prefer even columns (4 or 6). Examples: 10 cards → 5 + 5 (not 4 + 4 + 2); 7 cards → 4 + 3. If the count is awkward, adjust per-row count or trim to the essential cards.
Tables — one global .data-grid
Style all tables through a single class so they're consistent everywhere:
.data-grid thead th { font-size:10px; font-weight:500; letter-spacing:.05em;
text-transform:uppercase; color:var(--ink-soft); padding:.5rem 1rem .5rem 0;
text-align:left; border-bottom:1px solid var(--hairline); }
.data-grid tbody td { font-size:10px; color:var(--ink-soft); padding:.6875rem 1rem .6875rem 0; }
.data-grid tbody tr { border-bottom:1px solid var(--hairline-soft); }
.data-grid tbody tr:hover { background:color-mix(in srgb, var(--surface-muted) 50%, transparent); }
td.cell-name { color:var(--ink-strong); font-weight:500; font-size:11px; } /* main column — gray-700, 1px above base */
td.cell-id { color:var(--ink); font-weight:500; font-family:var(--font-mono); }
td.cell-amount, td.cell-strong { color:var(--ink-strong); font-weight:600; }
td.cell-faint { color:var(--ink-faint); } /* S/N */
td.cell-num, th.cell-num { text-align:right; } /* numerics right-aligned */
- Total count is prominent: at the top-left, above the table, just before the column header row, at 16–18px in the primary colour (e.g.
1,248 transactions). - Conventions: the main column (e.g. user's name, product name) uses
cell-name— gray-700, 11px, 1px larger than every other column; S/N column faint + right context; IDs/refs infont-mono; amounts right-aligned +cell-amount. - Header/cell alignment must never be set separately — pair them at the column level, not the row level. The bug this prevents: a numeric/money column's cells get right-aligned but its header is left where it was, so once the content (e.g.
$1,245,678.00) forces the column wider than its short header text, the label and the values visibly stop lining up. Do not hand-applytext-alignclasses to a<th>and its<td>s independently — model columns as a config array ({ key, label, align }) and render both the header row and every body row from that same array, so a column's alignment is defined exactly once and the header and its cells can never disagree. Any column withalign: 'right'(amounts, counts, dates formatted as numbers) getscell-numon both its<th>and every<td>in that column, sourced from the one config entry — never added by hand in two places.
Pagination (on every long list)
Any content with more than 10 rows gets pagination + numbering — even when it isn't a table (card lists, feeds, activity logs included). Replaces any "showing first N" text.
- Bar:
flex justify-between border-t border-hairline pt-3. - Left:
Showing {from}–{to} of {N} {noun}in 11pxink-muted. - Right: per-page stepper (bordered box: number + ▲▼ 8px arrows) → "per page" → prev
‹(small bordered square) → current-page chip (border-primary/30 bg-primary-tint text-primary-dark) → "of {totalPages}" → next›.
Page header (understated)
18px semibold ink-em title; optional one-line 12px ink-muted subtitle directly under it; a small inline muted icon if useful; actions right-aligned. No big gradient/icon-box header, no description block bloat.
Filters, search, selects
Compact: search h-8 11px border-border with a muted icon; filter selects h-8 11px. The filter bar is borderless — a plain flex row, not a boxed toolbar — and lives inside the table card, not floating above it.
Badges
rounded-full px-2 py-[1px] text-[10px] font-medium, using the semantic tint bg / mid text / faint border recipe. Hues kept distinct from the accent so status is never ambiguous.
Buttons & inputs
- Buttons: default
h-9 px-3.5 text-[13px] rounded-md; smh-8 text-[12px]. No heavy shadow, no active-scale bounce. - Inputs / selects:
h-9 text-[13px] rounded-md, focus ringprimary/15, placeholderink-faint. Field labelstext-[12px]ink-strong.
Charts
- Library: Nivo by default (gradient-friendly); Apache ECharts for complex viz (Sankey/flow, large time-series, heatmaps). Do not fall back to plain Recharts.
- Styling discipline (ported): axis ticks 10px in
ink-soft; grid = hairline, horizontal lines only, no axis lines; gradient fills on area/bar; tooltips 11px with a hairline border and ~8px radius. Pull all series/axis/grid colours from brand tokens — primary + accent lead, muted greys support. - Bar hover = the colour's 50 or 100 level (a light tint), not a darker full shade.
- Never stretch a chart's container to match a sibling's height and call it done. The bug this prevents: a chart card sits next to a taller card in a flex/grid row, the chart's wrapper is stretched (
h-full/align-items: stretch) to match it, but the chart library renders its bars/lines using its own internal sizing and doesn't actually fill that taller box — leaving a dead gap of empty space below the plotted content and above the card's bottom border/edge. Fix at the source: give every chart container an explicit, intentional height (chosen for that chart's own content, not inherited from a neighbour), and make sure the Nivo/ECharts responsive wrapper (ResponsiveBar,ResponsiveLine, ECharts' own resize handling) is configured to fill exactly that height, no more and no less. If two cards in a row genuinely need to match, decide the shared height up front as a deliberate design choice, not by letting one stretch to whatever the other's content happens to produce.
Forms
Centered per §3 when they don't fill the screen; grouped fields, generous spacing, one clear primary action.
9. AI (demo-only, always)
- Every dashboard gets its own custom AI assistant name (e.g. SwitchAI for Switchboard). Name it in the per-dashboard prompt.
- Never mention Claude or Anthropic anywhere in the UI, copy, or code comments.
- All AI features are pre-baked / mocked — no API keys, no real engine — and tie to the seed-data toggle so they always demo cleanly. Keep them light where the product calls for light (summarize, highlight, suggest).
10. Build order (every dashboard)
- Foundations: design tokens (gray 50–900, ink ladder, hairlines, main 25–900, base 9–10px type scale, mono, radius, header height, ~50px content offset — main dashboard exempt, uses ordinary ~20–28px padding), Tailwind theme, shadcn setup, auth/role context + real-time role switching (from Settings), seed data + seed toggle, chosen top-bar variant, independent sidebar scroll, global
.data-grid. - Build the first three of the twelve key pages — including the main dashboard — then git commit and push before proceeding.
- Build the remaining nine key pages (including the analytics page and the detail/profile page).
- Full nav/section list with tasteful placeholders for unbuilt sections.
- Light, pre-baked AI assistant.
- Polish: visual-hierarchy pass, empty/centered states, gradient charts, hover tints, pagination.
11. Per-dashboard prompt template
Each build prompt only needs to specify what's unique:
- Product & recontextualization — what it is, the problem, the industry.
- The 12 key pages — named, including the mandatory main dashboard + analytics + detail page.
- Full section/nav list — every group and item, so depth is visible (mark which are built).
- Brand tokens — typeface(s), main colour (base 500), theme (light/dark), and the assigned top-bar variant.
- AI assistant name + the 2–4 light AI touches.
- Anything that overrides a named rule in this document.
Worked example — Switchboard (AI provider console)
- 12 key pages: 1) Overview dashboard · 2) Provider Catalog · 3) Comparison · 4) Products & Routing · 5) Access & Audit · 6) Integration Docs (table-led) · 7) Provider Detail (detail/profile, drills from Provider Catalog) · 8) Cost & Usage (analytics) · 9) Uptime & Availability (card/status format) · 10) Change Feed / Notifications (timeline format) · 11) Add / Connect Provider (form) · 12) New Routing Rule (form).
- Full nav also shows (not built): Model Presets, Team & Permissions, Settings (holds role switch).
- Roles built: Product Admin, Lead Developer. Popup also lists: Super Admin, Product Developer.
- AI assistant: SwitchAI — pre-baked Change Digest, Comparison Highlights, Cost Anomaly, Provider Suggestion.
- Top-bar variant: v2 (greeting + centered search + date chip).
12. Do / Don't
Do: grey ink ladder for hierarchy · hairline dividers · 10px data · pagination on every long list · right-align numbers · mono for IDs/refs · one accent moment per screen.
Don't: colour-code for emphasis · heavy borders/shadows · icon tiles on every KPI · filled delta pills everywhere · text-sm inside table cells · big gradient page headers.