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-b hairline.
  • 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-r hairline. Nav items px-3 py-2.5 rounded-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 is space-y-5; card padding p-4–6; KPI grid gap-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-dark for 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.7rem for section/container cards; metric / stat cards capped at 4–8px (rounded-lg); controls rounded-md (~6px); badges rounded-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 big ink-em number + 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 in font-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-apply text-align classes 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 with align: 'right' (amounts, counts, dates formatted as numbers) gets cell-num on 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 11px ink-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; sm h-8 text-[12px]. No heavy shadow, no active-scale bounce.
  • Inputs / selects: h-9 text-[13px] rounded-md, focus ring primary/15, placeholder ink-faint. Field labels text-[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)

  1. 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.
  2. Build the first three of the twelve key pages — including the main dashboard — then git commit and push before proceeding.
  3. Build the remaining nine key pages (including the analytics page and the detail/profile page).
  4. Full nav/section list with tasteful placeholders for unbuilt sections.
  5. Light, pre-baked AI assistant.
  6. 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.