Stat

A KPI tile — a labelled figure with an optional signed delta and a slot for a trend sparkline.

import { ... } from '@corndev/ui'
Monthly revenue4.2M+12.4%vs last month
Active users12.9K+3.1%vs last week
Signups1,2840%vs last week
Churn rate2.4%−0.6%vs last quarter
p95 latency184−12msvs yesterday
Error rate0.8%+0.3%vs yesterday
Annual recurring revenue51.4M+18.2%year over year
Small842
Medium842
Large842

HTML

<div class="corn-stat" data-size="md">
  <span class="corn-stat-label">Monthly revenue</span>
  <span class="corn-stat-value">$4.2M</span>
  <span class="corn-stat-delta" data-trend="good">
    <span class="corn-stat-delta-arrow" aria-hidden="true">▲</span>+12.4%
    <span class="corn-stat-delta-period">vs last month</span>
  </span>
</div>

Classes

Class
.corn-stat
.corn-stat-label
.corn-stat-value
.corn-stat-delta
.corn-stat-delta-arrow
.corn-stat-delta-period
.corn-stat-trend

Attributes

OnAttributeValues
.corn-stat-deltadata-trendgood|bad|neutral
.corn-statdata-sizesm|md|lg
.corn-statdata-heropresent or absent

Vanilla JS

Required for behaviour — adds formats the value, resolves the delta intent, and builds any label, value or delta element the markup does not already provide.

import { createStat } from '@corndev/ui';
import '@corndev/ui/theme';

const el = document.querySelector('#target');
const instance = createStat(el, { /* options */ });

Options

OptionTypeDefaultDescription
labelstringundefinedShort name for the metric. Sentence case, no trailing colon.
valuenumber|stringundefinedThe figure. Numbers are compacted unless format is false.
deltanumberundefinedSigned change against a previous period. Omit for a tile with no comparison.
deltaSuffixstring'%'Unit rendered after the delta.
deltaPeriodstringundefinedWhat the delta compares against, e.g. ‘vs last week’.
goodDirection'up'|'down'|'neutral''up'Which direction counts as good. Set ‘down’ for metrics where falling is better (churn, latency, error rate).
formatbooleantrueAuto-compact numeric values: 1,284 / 12.9K / 4.2M.
localestringundefinedBCP-47 tag for number formatting. Defaults to the runtime locale.
size'sm'|'md'|'lg''md'
herobooleanfalseDisplay-size value. Use at most one per view.

React

Stat — from @corndev/ui-react.

The props are the same as the options above, plus className.

Default

Monthly revenue4.2M+12.4%vs last month
<Stat label="Monthly revenue" value={4200000} delta={12.4} deltaPeriod="vs last month" />

Direction is not the same as goodness

The delta’s colour is its direction multiplied by whether that direction is good. Churn rising is bad; latency falling is good. Set goodDirection="down" for metrics where a smaller number is the win — leave it at the default and the tile paints half of a real dashboard backwards.

The arrow always points the way the number actually moved. Only the colour changes.

Signups1,284+8.2%vs last week
Churn rate2.4%−0.6%vs last quarter
Error rate0.8%+0.3%vs yesterday
<Stat label="Signups" value={1284} delta={8.2} deltaPeriod="vs last week" />
<Stat label="Churn rate" value="2.4%" delta={-0.6} deltaPeriod="vs last quarter" goodDirection="down" />
<Stat label="Error rate" value="0.8%" delta={0.3} deltaPeriod="vs yesterday" goodDirection="down" />

Colour is never the only channel. The arrow glyph and the +/− sign carry the direction on their own, so the delta still reads in greyscale, in print, and under colour-vision deficiency.

Value formatting

Numbers are compacted once the digits crowd — below 10,000 they are grouped in full, because 1,284 reads better than 1.3K and keeps information people want at that magnitude.

InputRendered
12841,284
1290012.9K
42000004.2M

Pass a string to render it verbatim, or format={false} to keep a number unformatted. Formatting follows the runtime locale unless you pass locale.

Grouped1,284
Compacted12.9K
Verbatim string$4.2M
<Stat label="Grouped" value={1284} />
<Stat label="Compacted" value={12900} />
<Stat label="Verbatim string" value="$4.2M" />

Sizes

Small842
Medium842
Large842
<Stat label="Small" value={842} size="sm" />
<Stat label="Medium" value={842} size="md" />
<Stat label="Large" value={842} size="lg" />

Hero

The single figure a view leads with, at display size. Use at most one per view — a page of hero numbers has no lead at all.

Annual recurring revenue51.4M+18.2%year over year
<Paper padding="lg">
  <Stat label="Annual recurring revenue" value={51400000} delta={18.2} deltaPeriod="year over year" hero />
</Paper>

Trend slot

Children render under the figure in .corn-stat-trend, sized for a sparkline. A trend is optional — a bare tile is a complete, correct stat.

Typography

The value uses the font’s proportional figures. tabular-nums gives every digit the width of a 0, which reads loose at display sizes; reserve it for columns that must align vertically, like table rows and axis ticks.