Flux 2.0

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
html
<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>

Every part is a documented component: the Metric row, the Table and the Badge. The only layout is a grid with --flux-seam-gap between sections. Copy it, replace the figures, and keep the order.

Order of the page

Status first, work second, detail on demand.

  1. 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.
  2. Metric row. Three to five figures on one surface, each with the same comparison period. This is the only place for headline numbers.
  3. 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.
  4. Everything else. Full lists and reports belong on their own pages, linked from the metric or the section heading, not stacked below.
css
.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.

Careful
If every change on the row is green or red, the colour has stopped meaning anything. Most changes should be neutral, so the one that is not is seen.

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.