Meter
Small bars inside rows and panels. No axes; the number beside each bar carries its value.
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterBasic() {
return (
<Preview>
<Meter.Root value={0.48} />
</Preview>
);
}
- Use a meter for one value per row, inside a table, list or panel. For a value over time, use a Sparkline or a Chart.
Meter.Rootprintsvalue, a fraction from 0 to 1, as a percentage and fills the bar tovalue / max;maxdefaults to 1.- For row-relative bars, give every row the same
max, usually the largest row’s value, so the largest row fills its bar and the others stay comparable. SetshowValue={false}when the percent would read as the bar’s length. - Use the same scale for bars that people compare.
- Print percentages and values as text; a bar is never the only place a number lives.
Range
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterRange() {
return (
<Preview>
<Meter.Range domain={[10, 1000]} p50={20} p95={120} p99={480} />
<Meter.Range domain={[10, 1000]} failing={true} p50={120} p95={480} p99={1000} />
</Preview>
);
}
Meter.Range draws p50 to p95 as the main segment, p95 to p99 as the tail and a tick at p95. The scale is logarithmic, so domain starts above zero; values outside it are clamped to the edges. Share domain across rows. Set failing only when a row exceeds its limit; red is reserved for failing rows.
Distribution
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterDistribution() {
return (
<Preview stack={true}>
<Meter.Distribution fair={20} good={70} poor={10} />
</Preview>
);
}
Use Meter.Distribution for the good, fair and poor parts of a web vital. Each segment’s width is its share of the three values, so percentages and fractions both work; a part of 0 is left out. Keep the percentages as text next to the bar; colour and segment width cannot carry the values alone. Use size="sm", a thinner bar, inside table rows.
Part to whole
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterParts() {
return (
<Preview stack={true}>
<Meter.Parts
parts={Array.from({ length: 4 }, (_, index) => ({
id: `part-${index + 1}`,
label: `Part ${index + 1}`,
value: 48 / 2 ** index,
}))}
/>
</Preview>
);
}
Meter.Parts draws the parts in the order given, up to seven segments: with more than seven, the first six stay and the rest are grouped as Other. Pass them largest first. Segment n takes colour n of partsRampClassNames, so a table next to the bar can show the same swatch per row. The legend prints each value as a number; pass formatValue to show a percentage instead, and set legend={false} to draw the bar alone. Hover a segment or a legend item to highlight its counterpart.
Compare
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterCompare() {
return (
<Preview stack={true}>
<Meter.Compare
current={{ label: "Current", text: "120", value: 120 }}
previous={{ label: "Previous", text: "48", value: 48 }}
/>
</Preview>
);
}
Meter.Compare puts the current period above the previous one, each as a row of its label, a bar and its value as text. Both bars share one scale, the larger of the two values. The value column keeps a minimum width, so the bars of meters stacked in a column start and end together. The current bar takes tone, story, warning or danger, to match the metric’s health; the previous one stays in the grey of a data bar.
Scale
import { Meter } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function MeterScale() {
return (
<Preview stack={true}>
<Meter.Scale good={48} poor={120} value={72} />
</Preview>
);
}
Meter.Scale shows where a value sits against two limits, for a metric where lower is better, such as a web vital. The track has three equal segments: up to good, from good to poor, and past poor. The marker moves through each segment in proportion to value and stops at the end of the track, at one and a half times poor, so scales with different limits line up. Leave value out when there is no data. Print the value as text next to the scale.
API
valuenumber
On Root, a fraction from 0 to 1. On Scale, optional.
numbermax?number
On Root.
number1showValue?boolean
On Root.
booleantruep50number
On Range. Also `p95` and `p99`.
numberdomain[number, number]
On Range. Both above 0.
[number, number]failing?boolean
On Range.
booleanfalsegoodnumber
On Distribution. Also `fair` and `poor`.
numbersize?"sm" | "md"
On Distribution.
"sm" | "md""md"parts{ id: string; label: ReactNode; value: number }[]
On Parts.
{ id: string; label: ReactNode; value: number }[]formatValue?(value: number, total: number) => ReactNode
On Parts.
(value: number, total: number) => ReactNodelegend?boolean
On Parts.
booleantruecurrent{ label: ReactNode; text: ReactNode; value: number }
On Compare. Also `previous`.
{ label: ReactNode; text: ReactNode; value: number }tone?"story" | "warning" | "danger"
On Compare.
"story" | "warning" | "danger""story"poornumber
On Scale. Also `good`, in the units of `value`.
numberParts: Root, Compare, Distribution, Parts, Range, Scale. partsRampClassNames holds the segment colours of Parts, in order, for swatches that match them. Other props go to the native element.