Skip to content
RU
Scale
Density
Theme
Accent

Data display

Metric

One number people watch. The unit is set smaller and quieter than the number itself: "42 s" at one size reads as one word, and once size and colour are parted the eye grabs the number and the "s" recedes.

The component
Runs per day
128
18% against the last
Average time
4.2s
Warnings
5
5 new
Markup
<div class="inst-metric-row">
  <div class="inst-metric">
    <div class="inst-metric-label">Runs per day</div>
    <div class="inst-metric-value">128</div>
    <div class="inst-metric-delta" data-dir="down" data-tone="ok">18% against the last</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Average time</div>
    <div class="inst-metric-value">4.2<span class="inst-metric-unit">s</span></div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Warnings</div>
    <div class="inst-metric-value">5</div>
    <div class="inst-metric-delta" data-dir="up" data-tone="error">5 new</div>
  </div>
</div>
Example
Runs per day
128
Average time
4.2s
Warnings
5
Markup
<div class="inst-metric-row inst-metric-row--joined">
  <div class="inst-metric">
    <div class="inst-metric-label">Runs per day</div>
    <div class="inst-metric-value">128</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Average time</div>
    <div class="inst-metric-value">4.2<span class="inst-metric-unit">s</span></div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Warnings</div>
    <div class="inst-metric-value">5</div>
  </div>
</div>
Example
Success rate
94.2%
1.8 points in a week
Duration p95
4:12min
median 2:48
In the queue
37
9 waiting over an hour
Markup
<div class="inst-metric-row inst-metric-row--joined inst-metric-row--headline">
  <div class="inst-metric">
    <div class="inst-metric-label">Success rate</div>
    <div class="inst-metric-value">94.2<span class="inst-metric-unit">%</span></div>
    <div class="inst-metric-delta" data-dir="down" data-tone="warn">1.8 points in a week</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Duration p95</div>
    <div class="inst-metric-value">4:12<span class="inst-metric-unit">min</span></div>
    <div class="inst-metric-delta">median 2:48</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">In the queue</div>
    <div class="inst-metric-value">37</div>
    <div class="inst-metric-delta" data-dir="up" data-tone="warn">9 waiting over an hour</div>
  </div>
</div>

Contract

What Required Why
--joined for a band that is one fact yes Separate tiles say "a set of numbers", one parted surface says "the state of this screen". Picking the wrong one is not a style slip but a wrong sentence
inst-metric-label yes "128" with no answer to "of what" is not a metric
The sign of the change in words in the delta yes The arrow is a second carrier rather than the only one: "↓ 18%" reads without a tone too
An accessible name from the label and the number yes, if the metric is clickable Otherwise "128" is spoken with no answer to "of what"
data-dir and data-tone no Without the first the delta goes with no arrow, without the second in a neutral colour

Accessibility

The label and the number Tied visually by their order. If the metric is clickable, the accessible name has to include both — otherwise "128" is spoken with no answer to "of what"
Not colour alone The delta carries an arrow and the sign of the change in the text. A reader who does not tell tones apart reads "↓ 18%"
Contrast The delta takes --tone-ink — the threshold of text at 4.5:1 rather than of a mark at 3:1
The size of the number --text-lg inside a region, and the tile keeps a value/label ratio of 1.44. A metric there may not outrank the name of the screen it stands on — measured 2026-08-27. The one exception is --headline, below
--headline only under the page header, never inside a region yes
At most one --headline band per screen yes
Tabular figures On by default: a number updating in place does not tug at its neighbours

Anatomy

Direction and judgement are different attributes

This is the main trap of the component. An arrow down on the time of a run is good, an arrow up on warnings is bad. An attribute named after the arrow and meaning the judgement is a guaranteed mistake of application.

Attribute What it encodes Values
data-dir Which way it changed up · down
data-tone Whether that is good or bad ok · warn · error · neutral · running
Example
Run time
42 s
18% — it fell, and that is good
Warnings
12
5 new — it grew, and that is bad
Markup
<div class="inst-metric-row">
  <div class="inst-metric">
    <div class="inst-metric-label">Run time</div>
    <div class="inst-metric-value">42<span class="inst-metric-unit">&nbsp;s</span></div>
    <div class="inst-metric-delta" data-dir="down" data-tone="ok">18% — it fell, and that is good</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Warnings</div>
    <div class="inst-metric-value">12</div>
    <div class="inst-metric-delta" data-dir="up" data-tone="error">5 new — it grew, and that is bad</div>
  </div>
</div>

Both attributes are optional: a delta with no data-dir goes with no arrow, with no data-tone in a neutral colour.

Composition

A row of metrics — with no borders

Example
Total
128
Succeeded
121
Failed
7
Markup
<div class="inst-metric-row">
  <div class="inst-metric">
    <div class="inst-metric-label">Total</div>
    <div class="inst-metric-value">128</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Succeeded</div>
    <div class="inst-metric-value">121</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Failed</div>
    <div class="inst-metric-value">7</div>
  </div>
</div>

Four numbers are one group. A border around each turns them into four objects, between which the reader starts looking for differences that are not there. So a metric stands on a muted surface rather than in a card.

Patterns

The summary of a run

On its own a metric is just a large number; the meaning appears in a row.

What is visible only here:

  • none of them has a border. Four numbers are one group; a border around each would turn them into four objects, between which the reader starts looking for differences;
  • the unit is smaller and quieter than the number. "42 s" at one size reads as one word; once size and colour are parted the eye grabs the number and the "s" recedes;
  • direction and judgement are independent. An arrow down on the time is good, an arrow up on warnings is bad.
In context
Run time
42 s
18% against the last
Tests passed
248/251
3 failing
Bundle size
7.4 MB
unchanged
Warnings
12
5 new
Markup
<div class="inst-metric-row">
  <div class="inst-metric">
    <div class="inst-metric-label">Run time</div>
    <div class="inst-metric-value">42<span class="inst-metric-unit">&nbsp;s</span></div>
    <div class="inst-metric-delta" data-dir="down" data-tone="ok">18% against the last</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Tests passed</div>
    <div class="inst-metric-value">248<span class="inst-metric-unit">/251</span></div>
    <div class="inst-metric-delta" data-tone="warn">3 failing</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Bundle size</div>
    <div class="inst-metric-value">7.4<span class="inst-metric-unit">&nbsp;MB</span></div>
    <div class="inst-metric-delta">unchanged</div>
  </div>
  <div class="inst-metric">
    <div class="inst-metric-label">Warnings</div>
    <div class="inst-metric-value">12</div>
    <div class="inst-metric-delta" data-dir="up" data-tone="error">5 new</div>
  </div>
</div>

API

Classes
NameWhat it does
class
inst-metricOne metric
inst-metric-rowA row of metrics
inst-metric-labelThe label above the number
inst-metric-valueThe number itself
inst-metric-unitThe unit inside the number: smaller and quieter
inst-metric-deltaThe change against the previous period
modifier
inst-metric-row--joinedOne surface parted by rules instead of separate tiles
inst-metric-row--headlineThe band under a page header. The value takes the ceiling of the scale. One per screen
Attributes
NameValueWhat it does
attribute
data-dirup · downon inst-metric-delta
data-toneneutral · running · ok · warn · errorin the same place
Tokens
NameValueWhere from
variable
--metric-value-sizeHow loud the value is--text-lgthe application sets it
token
--text-2xl1.75rembase +4: scale
--text-2xs0.6875rembase +4: scale
--surface-recessedlight-dark(oklch(0 0 0 / 0.060), oklch(1 0 0 / 0.050))one across every cell
--radius-md8pxbase +4: scale
--pad-block-yvar(--space-4)base +14: density · scale
--gap-rowvar(--space-3)base +14: density · scale
--text-lg1.125rembase +4: scale
--text-xs0.78125rembase +4: scale
--tracking-tight-0.01emone across every cell
--tone-inkvar(--accent-text)base +2:
--size-chevron10pxbase +4: scale
--space-24pxone across every cell

Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument