Stat
One figure written large, with a label over it, a unit after it and a badge beside it.
import { Frame } from '@/components/panelui/frame';
import { RangeMeter } from '@/components/panelui/range-meter';
import { Stat } from '@/components/panelui/stat';
export default function StatStorage() {
return (
<Frame variant="band" className="w-full max-w-sm">
<Frame.Header>
<Frame.Title>Storage</Frame.Title>
<Frame.Action>Design team</Frame.Action>
</Frame.Header>
<Frame.Panel className="gap-4 p-5">
<Stat>
<Stat.Content>
<Stat.Label>Used</Stat.Label>
<Stat.Value>
68.4
<Stat.Unit>GB</Stat.Unit>
</Stat.Value>
</Stat.Content>
<Stat.Badge>31.6 GB free</Stat.Badge>
</Stat>
<div className="flex flex-col gap-2">
<RangeMeter
value={68}
color="info"
formatValue={(value) => `${value}%`}
label="Storage used"
valueText="68 percent used"
/>
<RangeMeter.Scale start="0 GB" end="100 GB" />
</div>
</Frame.Panel>
</Frame>
);
}Use it for the one number a panel is about: storage used, a response time, words written this month. The badge on the far side of the row says how the reading stands, such as "31.6 GB free" or "inside budget".
It shows a number and nothing else. It does not draw a trend, a sparkline or a target; put the chart that goes with the number under it, in the same panel.
For a metric with a trend, a sparkline or progress towards a target, use Kpi. For a number that rolls to its new value as it changes, put a Rolling Number inside Stat.Value.
Installation
npx shadcn@latest add https://panelui.dev/r/web/stat.jsonpnpm dlx shadcn@latest add https://panelui.dev/r/web/stat.jsonyarn dlx shadcn@latest add https://panelui.dev/r/web/stat.jsonbunx shadcn@latest add https://panelui.dev/r/web/stat.jsonThe source is copied into components/panelui/, where you can edit it.
npm install panelui-webpnpm add panelui-webyarn add panelui-webbun add panelui-webImport from panelui-web/stat instead of @/components/panelui/stat. Tailwind needs one line to read the package's classes, which is on the installation page.
Install the packages it uses:
npm install tailwind-variantspnpm add tailwind-variantsyarn add tailwind-variantsbun add tailwind-variantsCopy the source into components/panelui/stat.tsx:
Usage
import { Stat } from "@/components/panelui/stat"<Stat>
<Stat.Content>
<Stat.Label>Response time</Stat.Label>
<Stat.Value>
240<Stat.Unit>ms</Stat.Unit>
</Stat.Value>
</Stat.Content>
<Stat.Badge>60 ms inside budget</Stat.Badge>
</Stat>Composition
<Stat>
<Stat.Content>
<Stat.Label />
<Stat.Description />
<Stat.Value>
<Stat.Unit />
</Stat.Value>
</Stat.Content>
<Stat.Badge />
</Stat>Stat.Content— The label and the value, stacked. Use it on its own, withoutStatround it, for a figure with no badge.Stat.Label— What is measured, in a quiet line over the value.weight="medium"sets it heavier, for a figure that stands on its own rather than under a panel's title.Stat.Description— What the reading means, in words, under the label: "Answering slowly".Stat.Value— The figure.sizeissm,mdorlg, from 2xl to 4xl; the default islg.Stat.Unit— The unit after the number, written insideStat.Value. It takes its size and its gap from the value.Stat.Badge— How the reading stands, in a pill on the trailing side of the row.
Examples
Sizes
size on Stat.Value sets the figure at 2xl, 3xl or 4xl, and the unit is sized to match.
import { Stat } from '@/components/panelui/stat';
/** One reading at each size, largest first. */
const READINGS = [
{ size: 'lg', label: 'Orders today', value: '1,284' },
{ size: 'md', label: 'Average basket', value: '42.10', unit: 'EUR' },
{ size: 'sm', label: 'Returns', value: '2.4', unit: '%' },
] as const;
export default function StatSizes() {
return (
<div className="flex flex-wrap items-end justify-center gap-10">
{READINGS.map((reading) => (
<Stat.Content key={reading.label}>
<Stat.Label>{reading.label}</Stat.Label>
<Stat.Value size={reading.size}>
{reading.value}
{'unit' in reading ? <Stat.Unit>{reading.unit}</Stat.Unit> : null}
</Stat.Value>
</Stat.Content>
))}
</div>
);
}API Reference
Stat.Label
| Prop | Type | Default | Description |
|---|---|---|---|
weight | 'normal' | 'medium' | — | medium sets the label in a heavier weight, for a figure that stands on its own rather than at the top of a panel that is already titled. Default normal. |
Stat.Value
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | — | sm is 2xl, md 3xl and lg 4xl. Default lg. |
Every part also takes the props of the element it renders, and a className.
Notes
In a row and on its own
Inside Stat, the value is set with no leading, so the foot of the number lines up with the foot of the badge at the bottom of the row. A Stat.Content used on its own keeps the value's line height, which is what text beside it expects.
Changing values
Give Stat aria-live="polite" when the number changes as somebody uses the chart beside it, so a screen reader reads the new figure without cutting in on anything else.
Server Components
It holds no state and handles no events, so a Server Component can render it, dotted parts and all.