- ds
- Data display
- KPI and sparkline
KPI and sparkline
A number, its change and a small trend line.
The tiles at the top of a dashboard. Each answers one question, right now and compared with before: a label, the value, the change, a trend for the period, and the baseline the change is measured against.
Requests
2.73M 8.2%
vs 2.52M yesterday
Latency p95
212 ms 18%
objective 300 ms
Error rate
0.84% 0.5 pt
budget 0.5%, burning 1.7x
Spend, month to date
$6,140 3.1%
forecast $13.2k of $15k
Show code
<div class="kpis">
<div class="kpi">
<p class="label">Requests</p>
<p class="kpi-v">2.73M <span class="delta up good">8.2%</span></p>
<span class="spark" role="img" aria-label="Requests over 24 hours, rising"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path class="fill" d="M0 27.5L4.35 27.41L8.7 27.45L13.04 27.43L17.39 27.26L21.74 26.96L26.09 25.98L30.43 23.92L34.78 20.17L39.13 14.65L43.48 9.76L47.83 5.92L52.17 5.44L56.52 4.21L60.87 2.79L65.22 2.5L69.57 4.88L73.91 9.6L78.26 13.33L82.61 17.86L86.96 20.93L91.3 23.55L95.65 25.56L100 26.29L100 30L0 30Z"/><path d="M0 27.5L4.35 27.41L8.7 27.45L13.04 27.43L17.39 27.26L21.74 26.96L26.09 25.98L30.43 23.92L34.78 20.17L39.13 14.65L43.48 9.76L47.83 5.92L52.17 5.44L56.52 4.21L60.87 2.79L65.22 2.5L69.57 4.88L73.91 9.6L78.26 13.33L82.61 17.86L86.96 20.93L91.3 23.55L95.65 25.56L100 26.29"/></svg><i style="--y:0.1235"></i></span>
<p class="kpi-s">vs 2.52M yesterday</p>
</div>
<div class="kpi">
<p class="label">Latency p95</p>
<p class="kpi-v">212 <small>ms</small> <span class="delta up bad">18%</span></p>
<span class="spark s2" role="img" aria-label="p95 latency over 24 hours, rising at the end"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 25.23L4.35 27.18L8.7 24.58L13.04 24.25L17.39 23.28L21.74 24.9L26.09 21.66L30.43 24.9L34.78 25.23L39.13 27.5L43.48 24.25L47.83 25.88L52.17 26.85L56.52 25.23L60.87 22.31L65.22 16.14L69.57 9.32L73.91 2.5L78.26 8.99L82.61 17.76L86.96 24.9L91.3 26.85L95.65 23.28L100 26.2"/></svg><i style="--y:0.1266"></i></span>
<p class="kpi-s">objective 300 ms</p>
</div>
<div class="kpi down">
<p class="label">Error rate</p>
<p class="kpi-v">0.84<small>%</small> <span class="delta up bad">0.5 pt</span></p>
<span class="spark" role="img" aria-label="Error rate over 24 hours, climbing"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 27.5L4.35 24.94L8.7 24.94L13.04 23.01L17.39 22.37L21.74 21.09L26.09 18.53L30.43 17.88L34.78 18.21L39.13 17.24L43.48 16.92L47.83 17.56L52.17 17.24L56.52 15.64L60.87 15.64L65.22 14.68L69.57 13.4L73.91 13.4L78.26 10.83L82.61 8.59L86.96 7.31L91.3 6.99L95.65 3.46L100 2.5"/></svg><i style="--y:0.9167"></i></span>
<p class="kpi-s">budget 0.5%, burning 1.7x</p>
</div>
<div class="kpi">
<p class="label">Spend, month to date</p>
<p class="kpi-v">$6,140 <span class="delta down good">3.1%</span></p>
<span class="spark bars s3" role="img" aria-label="Daily spend for 14 days"><i style="--v:0.904"></i><i style="--v:0.900"></i><i style="--v:0.922"></i><i style="--v:0.937"></i><i style="--v:0.949"></i><i style="--v:0.974"></i><i style="--v:0.984"></i><i style="--v:1.000"></i><i style="--v:0.985"></i><i style="--v:0.965"></i><i style="--v:0.961"></i><i style="--v:0.968"></i><i style="--v:0.960"></i><i style="--v:0.951"></i></span>
<p class="kpi-s">forecast $13.2k of $15k</p>
</div>
</div>Tiles share hairlines instead of gaps, like metrics. The delta's arrow is the direction and its colour is the judgement: latency up is red, spend down is green. A tile in .down turns its label and trend red, for the one number that needs someone now.
Delta
Code
<span class="delta up good">12.4%</span>
<span class="delta down bad">3 nodes</span>
<span class="delta up bad">41 ms</span>
<span class="delta down good">$212</span>
<span class="delta">no change</span>
<span class="delta up good pill">8.2%</span>
<span class="delta down bad pill">2.1%</span>Sparkline
Code
<span class="spark" role="img" aria-label="Queue depth, rising"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 23.84L3.45 24.14L6.9 25.69L10.34 27.5L13.79 25.73L17.24 24.25L20.69 24.03L24.14 24.12L27.59 21.92L31.03 20.41L34.48 20.93L37.93 23.8L41.38 21.53L44.83 19.86L48.28 21.22L51.72 20.29L55.17 18.96L58.62 17.48L62.07 16.11L65.52 12.53L68.97 9.49L72.41 11.82L75.86 10.78L79.31 11.12L82.76 10.16L86.21 9.06L89.66 8.54L93.1 9.03L96.55 6.03L100 2.5"/></svg><i style="--y:0.9167"></i></span>
<span class="spark s3" role="img" aria-label="Lag, falling"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path class="fill" d="M0 20.81L3.45 27.5L6.9 26.51L10.34 24.13L13.79 18.95L17.24 18.13L20.69 8.35L24.14 7.96L27.59 8.1L31.03 14.92L34.48 12.89L37.93 15.21L41.38 20.88L44.83 21.83L48.28 18.32L51.72 19.28L55.17 22.35L58.62 20.87L62.07 16.26L65.52 13.88L68.97 13.62L72.41 19.2L75.86 13.94L79.31 17.5L82.76 14.71L86.21 12.7L89.66 9.58L93.1 2.5L96.55 2.97L100 15.25L100 30L0 30Z"/><path d="M0 20.81L3.45 27.5L6.9 26.51L10.34 24.13L13.79 18.95L17.24 18.13L20.69 8.35L24.14 7.96L27.59 8.1L31.03 14.92L34.48 12.89L37.93 15.21L41.38 20.88L44.83 21.83L48.28 18.32L51.72 19.28L55.17 22.35L58.62 20.87L62.07 16.26L65.52 13.88L68.97 13.62L72.41 19.2L75.86 13.94L79.31 17.5L82.76 14.71L86.21 12.7L89.66 9.58L93.1 2.5L96.55 2.97L100 15.25"/></svg><i style="--y:0.4915"></i></span>
<span class="spark c-down" role="img" aria-label="Failures"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 18.39L3.45 20.65L6.9 14.03L10.34 11.2L13.79 15.28L17.24 15.45L20.69 12.81L24.14 18.25L27.59 25.44L31.03 27.5L34.48 26.67L37.93 25.04L41.38 21.54L44.83 13.65L48.28 14.5L51.72 12.56L55.17 12L58.62 11.25L62.07 13.99L65.52 16.36L68.97 17.22L72.41 24.96L75.86 24.81L79.31 22.38L82.76 19.3L86.21 10.43L89.66 2.5L93.1 8.92L96.55 12.62L100 23.45"/></svg></span>
<span class="spark bars s2" role="img" aria-label="Runs per day"><i style="--v:0.684"></i><i style="--v:0.610"></i><i style="--v:0.653"></i><i style="--v:0.656"></i><i style="--v:0.551"></i><i style="--v:0.594"></i><i style="--v:0.604"></i><i style="--v:0.675"></i><i style="--v:0.786"></i><i style="--v:0.932"></i><i style="--v:0.875"></i><i style="--v:0.858"></i><i style="--v:0.865"></i><i style="--v:0.954"></i><i style="--v:0.961"></i><i style="--v:1.000"></i><i style="--v:0.993"></i><i style="--v:0.901"></i><i style="--v:0.829"></i><i style="--v:0.756"></i></span>An SVG with preserveAspectRatio="none", sized with --w and --h (6rem by 1.75rem). The last value is a dot, drawn in HTML so it stays round. Sparklines have no axes on purpose: they show shape, and the number next to them carries the value.
In a table
| Service | rpm | 24h | p95 |
|---|---|---|---|
| checkout-api | 4,210 | 212 ms | |
| search | 2,980 | 96 ms | |
| ledger-writer | 640 | 31 ms |
Show code
<table class="table compact">
<thead><tr><th>Service</th><th class="n">rpm</th><th>24h</th><th class="n">p95</th></tr></thead>
<tbody>
<tr><td>checkout-api</td><td class="n">4,210</td><td><span class="spark" style="--w:5rem;--h:1.25rem" role="img" aria-label="checkout-api traffic"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 27.29L4.35 27.5L8.7 27.34L13.04 27.31L17.39 27.18L21.74 26.86L26.09 25.81L30.43 23.44L34.78 19.38L39.13 15.2L43.48 9.79L47.83 5.54L52.17 3.25L56.52 3.27L60.87 2.5L65.22 2.92L69.57 3.42L73.91 8.11L78.26 12.64L82.61 17.01L86.96 20.76L91.3 23.49L95.65 25.45L100 26.37"/></svg><i style="--y:0.1211"></i></span></td><td class="n">212 ms</td></tr>
<tr><td>search</td><td class="n">2,980</td><td><span class="spark" style="--w:5rem;--h:1.25rem" role="img" aria-label="search traffic"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 27.5L4.35 27.43L8.7 27.49L13.04 27.46L17.39 27.32L21.74 27.09L26.09 26.04L30.43 23.93L34.78 20.2L39.13 14.64L43.48 10.54L47.83 6.43L52.17 5.3L56.52 4.81L60.87 3.02L65.22 2.5L69.57 5.24L73.91 9.22L78.26 13.63L82.61 17.94L86.96 21.4L91.3 23.89L95.65 25.75L100 26.5"/></svg><i style="--y:0.1165"></i></span></td><td class="n">96 ms</td></tr>
<tr><td>ledger-writer</td><td class="n">640</td><td><span class="spark s2" style="--w:5rem;--h:1.25rem" role="img" aria-label="ledger-writer traffic"><svg viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><path d="M0 26.47L4.35 27.5L8.7 26.8L13.04 23.98L17.39 22.08L21.74 22.91L26.09 24.36L30.43 24.25L34.78 21.83L39.13 20.52L43.48 18.47L47.83 14.71L52.17 16.56L56.52 16.13L60.87 13.97L65.22 9.67L69.57 13.19L73.91 15.18L78.26 16.63L82.61 12.33L86.96 8.26L91.3 7.71L95.65 2.5L100 8.91"/></svg><i style="--y:0.703"></i></span></td><td class="n">31 ms</td></tr>
</tbody>
</table>| Class | On | What it does |
|---|---|---|
.kpis | div | A row of tiles joined by hairlines. Columns fit from 12.5rem. |
.kpi | div | One tile: .label, .kpi-v, a .spark, .kpi-s. Works alone with its own border. .down for attention. |
.kpi-v | p | The value. small for the unit, a .delta after it. |
.kpi-s | p | What the change is measured against. |
.delta | span | Change. .up or .down for the arrow, .good or .bad for the colour, .pill for a tinted chip. |
.spark | span | Trend line. --w, --h, colour from a series or status class. path.fill for an area, <i> for the end dot at --y. |
.spark.bars | span | Columns instead of a line. Children are <i> with --v; the last is full strength. |
- Say what the change is against: yesterday, last week, the objective.
- Units in
small, so the number keeps its size. - Four to six tiles. Pick the ones someone acts on.
- Green for "up" by default. Decide per metric.
- A sparkline without the number it ends on.
- Percent changes on small counts: "3 nodes" says more than "+50%".
Accessibility
- Each sparkline has
role="img"and a label with the takeaway: "rising", "falling", "spike at 14:00". - The delta's direction is also in its text or label, not only in the arrow and colour.