Patterns
Dashboards
A dashboard answers two questions in order: how are things going, and what needs me now. Everything on the page serves one of those, and the second is usually the one people came for.
The recipe
Page header, one metric row, then the exceptions as a table.
September operations
Capacity, revenue and the departures that need someone today.
At a glance
- Berths sold
- 79.7%
- +3.2 pts vs last week
- Revenue booked
- ISK48.2m
- +6.1% vs last week
- Bookings on request
- 14
- +5 since Monday
- Cancellations
- 3
- −1 vs last week
Needs attention
| Reference | Departure | Issue | Status | Pax |
|---|---|---|---|---|
| KB-104931 | Golden Circle, 14 Sep | Guide not assigned | On request | 12 |
| KB-104940 | South Coast, 19 Sep | Hotel Kea has not confirmed | Awaiting supplier | 2 |
| KB-104963 | Diamond Circle, 28 Sep | Balance overdue | On request | 8 |
<div style="display: grid; gap: var(--flux-seam-gap)">
<header style="display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--flux-space-4)">
<div style="display: grid; gap: var(--flux-space-2)">
<h1 style="margin: 0; font-size: var(--flux-text-3xl)">September operations</h1>
<p class="flux-description" style="margin: 0">Capacity, revenue and the departures that need someone today.</p>
</div>
<button class="flux-button flux-button--secondary" type="button">Export report</button>
</header>
<section aria-labelledby="dash-glance" style="display: grid; gap: var(--flux-space-4)">
<h2 class="flux-visually-hidden" id="dash-glance">At a glance</h2>
<dl class="flux-metrics">
<div class="flux-metric">
<dt class="flux-metric__label">Berths sold</dt>
<dd class="flux-metric__value">79.7%</dd>
<dd class="flux-metric__change" data-tone="positive"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 12.5V3.5M4.5 7L8 3.5L11.5 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg><span>+3.2 pts</span> <span class="flux-metric__context">vs last week</span></dd>
</div>
<div class="flux-metric">
<dt class="flux-metric__label">Revenue booked</dt>
<dd class="flux-metric__value"><span class="flux-metric__unit">ISK</span>48.2m</dd>
<dd class="flux-metric__change" data-tone="positive"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 12.5V3.5M4.5 7L8 3.5L11.5 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg><span>+6.1%</span> <span class="flux-metric__context">vs last week</span></dd>
</div>
<div class="flux-metric">
<dt class="flux-metric__label">Bookings on request</dt>
<dd class="flux-metric__value">14</dd>
<dd class="flux-metric__change" data-tone="negative"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 12.5V3.5M4.5 7L8 3.5L11.5 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg><span>+5</span> <span class="flux-metric__context">since Monday</span></dd>
</div>
<div class="flux-metric">
<dt class="flux-metric__label">Cancellations</dt>
<dd class="flux-metric__value">3</dd>
<dd class="flux-metric__change"><svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 3.5V12.5M4.5 9L8 12.5L11.5 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /></svg><span>−1</span> <span class="flux-metric__context">vs last week</span></dd>
</div>
</dl>
</section>
<section aria-labelledby="dash-attention" style="display: grid; gap: var(--flux-space-4)">
<h2 id="dash-attention" style="margin: 0; font-size: var(--flux-text-xl)">Needs attention</h2>
<div class="flux-table-wrap">
<table class="flux-table" aria-labelledby="dash-attention">
<thead>
<tr>
<th scope="col">Reference</th>
<th scope="col">Departure</th>
<th scope="col">Issue</th>
<th scope="col">Status</th>
<th scope="col" data-numeric>Pax</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">KB-104931</th>
<td>Golden Circle, 14 Sep</td>
<td>Guide not assigned</td>
<td><span class="flux-badge flux-badge--pending">On request</span></td>
<td data-numeric>12</td>
</tr>
<tr>
<th scope="row">KB-104940</th>
<td>South Coast, 19 Sep</td>
<td>Hotel Kea has not confirmed</td>
<td><span class="flux-badge flux-badge--awaiting">Awaiting supplier</span></td>
<td data-numeric>2</td>
</tr>
<tr>
<th scope="row">KB-104963</th>
<td>Diamond Circle, 28 Sep</td>
<td>Balance overdue</td>
<td><span class="flux-badge flux-badge--pending">On request</span></td>
<td data-numeric>8</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>Order of the page
Status first, work second, detail on demand.
- Page header. A title that names the scope and period — "September operations", not "Dashboard" — and one line saying what the page is for. Page-level actions such as export sit at the end of the header row.
- Metric row. Three to five figures on one surface, each with the same comparison period. This is the only place for headline numbers.
- The exceptions. The records someone has to act on, as a table with a status badge and the reason in words. If nothing needs attention, say so in the table’s empty state rather than hiding the section.
- Everything else. Full lists and reports belong on their own pages, linked from the metric or the section heading, not stacked below.
.dashboard {
display: grid;
gap: var(--flux-seam-gap);
}Choosing metrics
A metric earns its place if someone would act differently when it moves.
- Three to five. Past five, none of them is headline and the row wraps into a grid nobody reads.
- One comparison period. "vs last week" on every change, so the changes can be compared with each other.
- Units in the value. "ISK 48.2m", "79.7%", "19 of 23". A bare number makes the reader guess.
- Counts that lead to work. "Bookings on request: 14" is useful because the table below lists them.
Tone of a change
Colour follows the consequence, not the arrow.
An arrow shows direction; the tone says whether that direction is good. More bookings on request is an upward arrow and negative news. Fewer cancellations is a downward arrow, and usually needs no colour at all because nobody has to act. Leave a change neutral unless the colour tells someone something.
Charts
Below the metrics, at full width, saying what it shows.
If a dashboard needs a trend or a share, use the Chartcomponent below the metric row, at full width. Its caption states the takeaway in a sentence, and its series take the --flux-chart-* roles, so it re-colours with the theme. Do not bring in a chart library’s default palette.
Do and don’t
Do
- Name the scope and period in the page title.
- Keep headline figures in one Metric row on one surface.
- Put the records that need action directly below the metrics.
- State the reason for each exception in words, beside its status badge.
- Leave changes neutral unless the colour tells someone to act.
Don’t
- Do not give every figure its own card.
- Do not show more than five metrics at the top.
- Do not add icon tiles, coloured bars or sparklines to metrics as decoration.
- Do not stack full reports below the exceptions. Link to them.
- Do not shrink type or spacing to fit the whole dashboard above the fold.