Designing dashboards
Blocks and expressions tell you what you can build. This page is about deciding what you should build: which panels deserve their place, which chart form fits which question, and the conventions that make a page trustworthy — for a person glancing at it and for an agent reading it as context. None of this is specific to one integration; the installed packs follow these rules, and pages you author alongside them should too.
The packs carry a second responsibility: they are reference implementations. When an agent is asked to build a page, the packs are what it studies — so they are held to reference quality throughout. An entity page deserves the same design attention as a hub; depth everywhere, and on every page the right tool for that page’s job rather than a house pattern repeated.
Serve the use case
Every panel must answer one question: what does this tell the reader, in this context? If the honest answer is “nothing they’d act on”, the panel goes — no matter how standard it looks.
The most common failure is borrowing a layout instead of designing one: a row of stat tiles on top because other pages have one, a request-rate column because dashboards usually show request rates. A current request rate in a service inventory tells the reader almost nothing — it has no baseline, no shape, no consequence. The same slot spent on a trend shows spikes, dips and step changes at a glance; a time-over-time comparison says whether now is unusual; a rank says where to look first. Prefer them in that spirit:
- Levels belong only when the number itself is the page’s answer (revenue today, orders in the window, disk used). Pair a level with its change (a paired query against the previous period) or its shape (a sparkline) so it carries context as well as magnitude.
- Trends are the default way to show activity — shape is information.
- Comparisons (previous period, sibling entities, before/after a change) are how a reader judges without a threshold.
- Ranks and digests (worst movers, clustered failures) direct attention; they answer “where do I look” rather than “what is the value”.
A page has one job. Panels that serve a different job belong on a different page — link there instead.
Compose UIs
Dashboard tools train authors into “one panel, one visualization” — a grid of isolated charts. Frames have no such constraint, and pages shouldn’t act as if they do: you are building a UI rather than a dashboard. A panel is a block tree; when one question needs several forms, they belong in one composed surface rather than four grid cells.
The shape to aim for: a usage card that stacks limit meters with their reset times as captions, a compact key-value cost strip, a per-category breakdown table, and a ranked “what’s using it?” list whose rows carry inline bars and drill-in links — the whole card re-scoped by a window and a category selector in its header. One card, one question, five forms, and every element passes the context test.
Shared context keeps the card reactive without duplication:
- A selector publishes state (
@expr/url_state,@block/switch/@block/option) and every query in the card reads it — one card that re-scopes instead of N near-identical panels. - A shared
@block/contextquery feeds several elements (the stat row, the table, the spark) from one scan. - Tables are composed UI too: block columns carry badges, links, sparks, and peek buttons, so a listing row is a small UI in its own right.
- Drawers and peeks are part of the vocabulary: detail on demand beats a permanently rendered detail panel.
Use the grid to lay out questions; slicing one question into chart-sized fragments defeats the composition.
Verdicts need owned thresholds
A panel may render a judgment — a red tint, a pass/fail badge — only when the threshold has an external owner: Core Web Vitals against the published thresholds, usage against a Kubernetes request or limit someone declared, HTTP 5xx by protocol semantics. Everything else shows level, change, and rank, and leaves judgment to the reader.
Do not invent thresholds to make a page feel decisive. A “healthy / degraded” chip computed from an arbitrary cutoff is an alert detector presented as a chart — alerting and SLOs own those judgments; a fake verdict is worse than none, because it trains readers to trust tints that nothing stands behind.
Let the mark follow the reading task
Readers decode position and length far more accurately than angle, area, or color. Choose the mark by what the reader must do with it:
| Reading task | Mark | Notes |
|---|---|---|
| Follow a value over time | line_y / area_y | Area when the quantity is a volume; line otherwise |
| Composition over time | stacked area_y | Only for parts of a whole, at most ~4 categories; use the semantic palette |
| Compare magnitudes across entities | bar_x / bar_y or a ranked table | Avoid stacked areas and many hues on one chart |
| Compare distributions’ tails | multi-line_y percentiles (p50/p95/p99) | Direct labels, shared scale |
| See a distribution over time | cell heatmap plus a collapsed histogram beside it | The one sanctioned heavy-color use: the task is pattern detection, the histogram supplies the readout |
| One value with context | @block/stat + paired delta, optionally a spark | |
| Share of a flat whole | bars | Avoid arcs and pies — angle reads poorly |
| Share of a hierarchy | treemap plus an adjacent table | Area reads poorly; the table is the readout |
Two hard rules and one distinction:
- More than ~5 series on one chart → small multiples rather than more hues. Use the plot’s
fx/fyfacet channels, or@block/forwhen each multiple needs its own query. (@block/facetis a filter control: it narrows data and does not lay out multiples.) - No gauges, dials, dual axes, or 3D. Angle and non-shared scales are the least accurate encodings, and dual axes let two unrelated shapes fake a correlation.
- Direct labels up to ~3 series (the mark
label); a legend beyond that.
Color means something
- Red means failing — and nothing else does. Outcome splits, severity badges, and error strips own the alarm hues; never use red or green decoratively. On a calm page, one saturated element is the “look here”; that only works if saturation is rare.
- Categorical hue means identity (a service, a partition, a state value) and stays consistent for the same identity across panels.
- Everything else stays muted — comparison backgrounds, gridwork, chrome.
Spend ink — and tokens — on signal
- Descriptions do the explaining; the chart doesn’t need annotation clutter, and the panel doesn’t need a paragraph of chrome. Write the description as the reading: what the panel shows, what a pattern implies, and the known trap (see below).
- Sparklines are word-sized charts: a trend column in a listing tells more than three numeric columns, at a fraction of the space. Prefer a spark plus one or two decisive numbers over a wall of figures.
- Keep tables
compact, drop card chrome ("chrome": false) when a block is embedded inside another surface, and paginate below the fold rather than truncating silently. - The same economy serves agents: a page’s queries and descriptions are context an agent pays for by the token. A curated panel with a self-explaining description is cheap, high-signal context; a 500-row dump is expensive noise.
Be honest
- Any panel that samples, caps, defers, or approximates says so in its description, with the rate or bound (“complete below 1 M spans, 1/8 trace-coherent sample above”).
- Bars start at zero. Log-spaced buckets say they are log-spaced. A clipped axis says it is clipped.
- An empty result renders an
emptymessage that states what absence means (“no failed checkouts in the window”) instead of a blank panel.
Explain on the page, explore from it
Pages are explanatory: the title is a claim (“Why checkouts fail”), and the description says how to read the panel, what each pattern implies, and the trap that would mislead (“failed checkouts often finish faster — they bail at the broken stage”). Written this way, descriptions double as interpretation rules an agent can apply to the same data.
Exploration happens on the page rather than at a separate destination: brush a heatmap to filter the tables next to it, regroup a chart with a switch, peek an entity in a drawer before committing to navigation, follow a row into the entity’s own page. Panels sharing a time axis share a crosshair — they are one scene. For open-ended narrative work — an investigation write-up, a hypothesis walked through with live charts — reach for a notebook (.md with live blocks) rather than bending a dashboard into an essay.
Structure: entities are routes, tabs are perspectives
- Every entity noun gets a listing route with the entity page beneath it (
services→services/{ServiceName}); an inventory is a place you can navigate to and link at, rather than a tab you must find. - Tabs are ways of looking at one thing (Overview, Reliability, Traces, Logs); buckets of entities belong in listing routes instead. Keep only the perspectives that are useful for that page; a tab that would render empty or generic for the entity is a tab it shouldn’t have.
- A row click means one thing. A table takes a whole-row click only when the row has a single subject (navigate to it or peek it). A row carrying two or more identities gets one link column per identity and no row click.
- Prefer a peek (drawer with the entity’s essentials and an “open full page” link) where the reader is scanning; full navigation where they are committing.