Meter
A static measurement inside a known range.
import { Div, Span } from "@implementjs/core";
import { Meter } from "@/lib/components/ui/meter";
export default function MeterDemo() {
const value = 3000;
const max = 4000;
return Div(
{ class: "flex w-full max-w-sm flex-col gap-2" },
Div(
{ class: "flex items-center justify-between text-sm font-medium" },
Span({ id: "tokens-label" }, "Tokens used"),
Span({ class: "tabular-nums" }, `${value} / ${max}`),
),
Meter({
"aria-labelledby": "tokens-label",
"aria-valuetext": `${value} of ${max} tokens used`,
value,
max,
}),
);
}Installation
npx jsrepo add @implementjs/ui/meter
Nothing else comes with it — this one stands alone on @implementjs/core and @implementjs/primitives.
Copy the file below to src/lib/components/ui/meter.ts. It imports cn, so copy utils.ts to src/lib/utils.ts too.
import { derived, Div, signal, type ComponentProps } from "@implementjs/core";
import { Meter as MeterPrimitive } from "@implementjs/primitives";
import { createComponent } from "@implementjs/primitives";
import { cn } from "@/lib/utils";
export type MeterProps = ComponentProps<typeof MeterPrimitive>;
export const Meter = createComponent(function Meter({
class: className,
value = 0,
min = 0,
max = 100,
...props
}: MeterProps) {
const valueSignal = signal(value);
const minSignal = signal(min);
const maxSignal = signal(max);
const percent = derived([valueSignal, minSignal, maxSignal], (value, min, max) => {
if (max === min) return 0;
return Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));
});
return MeterPrimitive(
{
value: valueSignal,
min: minSignal,
max: maxSignal,
...props,
"data-slot": "meter",
class: cn("relative h-2 w-full overflow-hidden rounded-full bg-primary/20", className),
},
Div({
"data-slot": "meter-indicator",
class: "size-full flex-1 bg-primary transition-transform",
style: { transform: percent.bind((p) => `translateX(-${100 - p}%)`) },
}),
);
});Usage
A meter is a reading, not a task: disk used, tokens spent, a score out of ten. For something that runs from start to finish, use progress instead.
The wrapper renders the indicator for you and drives it from value, min, and max — the component is one call with no children.
import { Meter } from "@/lib/components/ui/meter";
Meter({ value: 3000, max: 4000, "aria-label": "Tokens used" });
Labelling it
A meter is usually read alongside its numbers. Put both in a row above the bar and point the meter at the label:
Div(
{ class: "grid gap-2" },
Div(
{ class: "flex justify-between text-sm" },
Span({ id: "tokens" }, "Tokens used"),
Span({ class: "text-muted-foreground tabular-nums" }, "3,000 / 4,000"),
),
Meter({ value: 3000, max: 4000, "aria-labelledby": "tokens" }),
);
API Reference
Every prop the styling does not consume is forwarded to the Meter primitive, so the tables below are the whole surface — the behavior props and the styling ones together.
Meter
A static measurement within a known range. Sets role="meter" and the aria value attributes. Give it a track and a fill; it handles the semantics. Styled as a rounded track. The wrapper renders the indicator inside it and drives its position from value, min, and max. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Signal<number> | number | 0 | The current value. Pass a signal to control it from outside; a number seeds uncontrolled state. |
min | Signal<number> | number | 0 | The lowest value the meter can take. |
max | Signal<number> | number | 100 | The highest value the meter can take. |
| Data attribute | Value |
|---|---|
[data-meter-root] | Present |
[data-value] | The current value |
[data-min] | The minimum value |
[data-max] | The maximum value |