Bars and meters
tier 2 — classesThe small charts a page of figures is made of: a list of bars to compare a handful of values, one bar split into parts that add up, a value against a scale with the thresholds marked, and a bar or a meter against its previous value. Each is markup and classes; the one number only the app knows is an inline custom property.
currentColor, so which colour it is comes from one
set of modifiers that a bar, a segment, a meter fill, a sparkline
or a legend swatch all take. .series-1, .series-2,
.series-3, .series-4, .series-5 and
.series-6 are the categorical run — the brand first, then hues ordered
so neighbours differ — for parts that are merely different from each other.
.series-go, .series-warn, .series-danger,
.series-info and .series-muted are the semantic families, for a part that means something. Never colour a
category with a semantic family: a red slice says “danger” whether or not the
data does. The brand is the default, so one series needs no modifier.
A list of bars
.bar-list is rows of label, bar and value, for comparing a few things down a
column. The fill is --value, a percentage of the widest; the value is text
as well as a bar, because the number is what the reader takes away and the bar is for the
comparison. aria-current="true" on a row is the one the figure above is
about, and --bar-list-label on the list widens the label column.
Open orders by region
<div class="card" style="max-width:520px">
<div class="card-head"><h3>Open orders by region</h3></div>
<div class="card-body">
<div class="bar-list">
<div class="bar-list-row" aria-current="true">
<span class="bar-list-label">EU-Central</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 100%"></span></span>
<span class="bar-list-value">1 240</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">EU-West</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 62%"></span></span>
<span class="bar-list-value">769</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Nordics</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 31%"></span></span>
<span class="bar-list-value">384</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Iberia</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 18%"></span></span>
<span class="bar-list-value">223</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Other</span>
<span class="bar-list-track"><span class="bar-list-fill" style="--value: 6%"></span></span>
<span class="bar-list-value">74</span>
</div>
</div>
</div>
</div>
Bars that mean something
The same list with the semantic families on the fills. Here the rows are outcomes, so
the colour is a judgement the reader already makes — and .series-muted
is for the part that is not yet anything.
Carrier performance, last 7 days
<div class="card" style="max-width:520px">
<div class="card-head"><h3>Carrier performance, last 7 days</h3></div>
<div class="card-body">
<div class="bar-list" style="--bar-list-label: 11rem">
<div class="bar-list-row">
<span class="bar-list-label">Delivered on time</span>
<span class="bar-list-track"><span class="bar-list-fill series-go" style="--value: 91%"></span></span>
<span class="bar-list-value">91%</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Delivered late</span>
<span class="bar-list-track"><span class="bar-list-fill series-warn" style="--value: 7%"></span></span>
<span class="bar-list-value">7%</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Lost or damaged</span>
<span class="bar-list-track"><span class="bar-list-fill series-danger" style="--value: 2%"></span></span>
<span class="bar-list-value">2%</span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Not yet scanned</span>
<span class="bar-list-track"><span class="bar-list-fill series-muted" style="--value: 4%"></span></span>
<span class="bar-list-value">4%</span>
</div>
</div>
</div>
</div>
One bar in parts
.stack-bar is one bar whose segments add up: the states an order book is in,
the share each carrier has. Each .stack-bar-segment is --value,
and where they do not reach 100% the track shows through — the honest picture of a
total that is not fully accounted for. The bar is one picture, so it takes
role="img" and a label that says the numbers; the
.stack-bar-legend under it is the same numbers for the eye, each key with a
.stack-bar-swatch in the segment's colour. .stack-bar--lg is the
taller track for a bar that is the point of a card, and .meter--lg the same
for a meter.
<div class="sedna-col sedna-gap-3" style="max-width:520px">
<div class="card">
<div class="card-body">
<div class="stat">
<span class="stat-label">Orders by state</span>
<span class="stat-value">128</span>
</div>
<div class="stack-bar" style="margin-top: var(--space-5)" role="img" aria-label="128 orders: 79 decided, 36 waiting, 13 on hold">
<span class="stack-bar-segment series-go" style="--value: 62%"></span>
<span class="stack-bar-segment series-warn" style="--value: 28%"></span>
<span class="stack-bar-segment series-muted" style="--value: 10%"></span>
</div>
<div class="stack-bar-legend">
<span class="stack-bar-key"><span class="stack-bar-swatch series-go"></span> Decided 79</span>
<span class="stack-bar-key"><span class="stack-bar-swatch series-warn"></span> Waiting 36</span>
<span class="stack-bar-key"><span class="stack-bar-swatch series-muted"></span> On hold 13</span>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="stat">
<span class="stat-label">Volume by carrier</span>
<span class="stat-value">2 690 <span class="stat-unit">parcels</span></span>
</div>
<div class="stack-bar stack-bar--lg" style="margin-top: var(--space-5)" role="img" aria-label="2 690 parcels: 41% Northwind Express, 27% Nordic Post, 19% Iberia Courier, 13% others">
<span class="stack-bar-segment series-1" style="--value: 41%"></span>
<span class="stack-bar-segment series-2" style="--value: 27%"></span>
<span class="stack-bar-segment series-3" style="--value: 19%"></span>
<span class="stack-bar-segment series-4" style="--value: 13%"></span>
</div>
<div class="stack-bar-legend">
<span class="stack-bar-key"><span class="stack-bar-swatch series-1"></span> Northwind Express 41%</span>
<span class="stack-bar-key"><span class="stack-bar-swatch series-2"></span> Nordic Post 27%</span>
<span class="stack-bar-key"><span class="stack-bar-swatch series-3"></span> Iberia Courier 19%</span>
<span class="stack-bar-key"><span class="stack-bar-swatch series-4"></span> Others 13%</span>
</div>
</div>
</div>
</div>
A value against its thresholds
.meter differs from a progress bar in what the marks
say: progress ends at 100% and is done, a meter has a line the reader does not want the
fill to cross. Each .meter-mark is at --at, and
.meter-mark-label names it; --labelled on the meter reserves the
line under the track for those labels, and .meter-scale is the pair of end
values under that. Where two marks sit close, .meter-mark--above moves one
label over the track. The fill's colour is the app's judgement of the value — a series
modifier — and the marks stay neutral.
<div class="card" style="max-width:520px">
<div class="card-body sedna-col sedna-gap-3">
<div class="stat">
<span class="stat-label">Disk on src-db-14</span>
<span class="stat-value">71<span class="stat-unit">%</span></span>
<span class="stat-target">284 of 400 GB</span>
</div>
<div class="meter meter--labelled" role="img" aria-label="71% used; alert at 80%, critical at 90%">
<span class="meter-fill series-warn" style="--value: 71%"></span>
<span class="meter-mark" style="--at: 80%"><span class="meter-mark-label">alert 80%</span></span>
<span class="meter-mark meter-mark--above" style="--at: 90%"><span class="meter-mark-label">critical 90%</span></span>
</div>
<div class="meter-scale"><span>0 GB</span><span>400 GB</span></div>
</div>
</div>
Meters in a table
Without labels, a meter is a cell: the mark is still the cap, the fill's family says how close each queue is, and the figures in the next column are the numbers. Give the column a width, or the meters shrink to the longest name.
Queues against their caps
| Queue | Load | Cap |
|---|---|---|
| Orders | 38 / 120 | |
| Exports | 17 / 20 | |
| Notifications | 496 / 500 |
<div class="card" style="max-width:560px">
<div class="card-head"><h3>Queues against their caps</h3></div>
<table class="table">
<thead><tr><th>Queue</th><th style="width:45%">Load</th><th class="col-num">Cap</th></tr></thead>
<tbody>
<tr>
<td>Orders</td>
<td><div class="meter" role="img" aria-label="38 of 120"><span class="meter-fill" style="--value: 32%"></span><span class="meter-mark" style="--at: 85%"></span></div></td>
<td class="col-num">38 / 120</td>
</tr>
<tr>
<td>Exports</td>
<td><div class="meter" role="img" aria-label="17 of 20"><span class="meter-fill series-warn" style="--value: 85%"></span><span class="meter-mark" style="--at: 85%"></span></div></td>
<td class="col-num">17 / 20</td>
</tr>
<tr>
<td>Notifications</td>
<td><div class="meter" role="img" aria-label="496 of 500"><span class="meter-fill series-danger" style="--value: 99%"></span><span class="meter-mark" style="--at: 85%"></span></div></td>
<td class="col-num">496 / 500</td>
</tr>
</tbody>
</table>
</div>
This year against last
Use a comparison where the question is “better or worse than before” rather than
“how much”: this year against last, this month against the one before. A
.bar-previous goes first in the track, at its own --value, and the
fill after it paints over it. Where the previous value was higher its hatch sticks out past
the fill; where it was lower the fill covers the hatch and the line at its end is still
there. .bar-list-previous puts the previous figure beside the current one, and a
.stack-bar-legend with a .stack-bar-swatch--previous says which is
which — without it a reader takes the hatch for a second series. Give each track
role="img" and a label that states both values, with the period each belongs to.
Delivered on time, by region
<div class="card" style="max-width:520px">
<div class="card-head"><h3>Delivered on time, by region</h3></div>
<div class="card-body">
<div class="bar-list" style="--bar-list-label: 6rem">
<div class="bar-list-row">
<span class="bar-list-label">EU-Central</span>
<span class="bar-list-track" role="img" aria-label="72% this year, 64% last year">
<span class="bar-previous" style="--value: 64%"></span>
<span class="bar-list-fill" style="--value: 72%"></span>
</span>
<span class="bar-list-value">72%<span class="bar-list-previous">(64%)</span></span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">EU-West</span>
<span class="bar-list-track" role="img" aria-label="58% this year, 81% last year">
<span class="bar-previous" style="--value: 81%"></span>
<span class="bar-list-fill" style="--value: 58%"></span>
</span>
<span class="bar-list-value">58%<span class="bar-list-previous">(81%)</span></span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Nordics</span>
<span class="bar-list-track" role="img" aria-label="89% this year, 88% last year">
<span class="bar-previous" style="--value: 88%"></span>
<span class="bar-list-fill" style="--value: 89%"></span>
</span>
<span class="bar-list-value">89%<span class="bar-list-previous">(88%)</span></span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Iberia</span>
<span class="bar-list-track" role="img" aria-label="41% this year, 47% last year">
<span class="bar-previous" style="--value: 47%"></span>
<span class="bar-list-fill" style="--value: 41%"></span>
</span>
<span class="bar-list-value">41%<span class="bar-list-previous">(47%)</span></span>
</div>
</div>
<div class="stack-bar-legend">
<span class="stack-bar-key"><span class="stack-bar-swatch"></span> This year</span>
<span class="stack-bar-key"><span class="stack-bar-swatch stack-bar-swatch--previous"></span> Last year</span>
</div>
</div>
</div>
Month against month, with the change
When the change matters more than the old figure, put a .stat-delta in the value
column instead of .bar-list-previous. The arrow carries the direction and the
modifier the judgement, exactly as under a stat, and a change too small to judge takes
no modifier. The hatch is neutral whatever series the fill is; give
.bar-previous the fill's .series-* only where both bars must share
a hue.
Parcels by carrier, September
<div class="card" style="max-width:520px">
<div class="card-head"><h3>Parcels by carrier, September</h3></div>
<div class="card-body">
<div class="bar-list" style="--bar-list-label: 7.5rem">
<div class="bar-list-row">
<span class="bar-list-label">Northwind Express</span>
<span class="bar-list-track" role="img" aria-label="1 104 parcels in September, 948 in August">
<span class="bar-previous" style="--value: 86%"></span>
<span class="bar-list-fill series-2" style="--value: 100%"></span>
</span>
<span class="bar-list-value">1 104<span class="stat-delta stat-delta--good"><i class="ri-arrow-up-line" aria-hidden="true"></i>16%</span></span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Nordic Post</span>
<span class="bar-list-track" role="img" aria-label="726 parcels in September, 812 in August">
<span class="bar-previous" style="--value: 74%"></span>
<span class="bar-list-fill series-2" style="--value: 66%"></span>
</span>
<span class="bar-list-value">726<span class="stat-delta stat-delta--bad"><i class="ri-arrow-down-line" aria-hidden="true"></i>11%</span></span>
</div>
<div class="bar-list-row">
<span class="bar-list-label">Iberia Courier</span>
<span class="bar-list-track" role="img" aria-label="511 parcels in September, 498 in August">
<span class="bar-previous" style="--value: 45%"></span>
<span class="bar-list-fill series-2" style="--value: 46%"></span>
</span>
<span class="bar-list-value">511<span class="stat-delta"><i class="ri-arrow-up-line" aria-hidden="true"></i>3%</span></span>
</div>
</div>
<div class="stack-bar-legend">
<span class="stack-bar-key"><span class="stack-bar-swatch series-2"></span> September</span>
<span class="stack-bar-key"><span class="stack-bar-swatch stack-bar-swatch--previous"></span> August</span>
</div>
</div>
</div>
A meter or a progress bar against before
.bar-previous works the same way as the first child of a .meter or a
.progress: disk now against a week ago, a sprint against the last one on the
same day. On a meter the previous value's line sits beside the threshold marks, so keep the
legend. A .stack-bar takes no previous value: its parts are shares of one
total, and two totals compare better as two stack bars, one above the other.
<div class="sedna-col sedna-gap-3" style="max-width:520px">
<div class="card">
<div class="card-body sedna-col sedna-gap-3">
<div class="stat">
<span class="stat-label">Disk on src-db-14</span>
<span class="stat-value">71<span class="stat-unit">%</span></span>
<span class="stat-target">77% a week ago</span>
</div>
<div class="meter meter--labelled" role="img" aria-label="71% used now, 77% a week ago; alert at 80%">
<span class="bar-previous" style="--value: 77%"></span>
<span class="meter-fill series-warn" style="--value: 71%"></span>
<span class="meter-mark" style="--at: 80%"><span class="meter-mark-label">alert 80%</span></span>
</div>
<div class="stack-bar-legend">
<span class="stack-bar-key"><span class="stack-bar-swatch series-warn"></span> Now</span>
<span class="stack-bar-key"><span class="stack-bar-swatch stack-bar-swatch--previous"></span> A week ago</span>
</div>
</div>
</div>
<div class="card">
<div class="card-body sedna-col sedna-gap-3">
<div class="stat">
<span class="stat-label">Tickets closed this sprint</span>
<span class="stat-value">44 <span class="stat-unit">of 80</span></span>
<span class="stat-target">52 closed by the same day last sprint</span>
</div>
<div class="progress" role="img" aria-label="44 of 80 closed; 52 closed by the same day last sprint">
<span class="bar-previous" style="--value: 65%"></span>
<div class="progress-bar" style="width: 55%"></div>
</div>
</div>
</div>
</div>