Progress
Show how far a task has advanced toward completion.
import { Div, ImplementLifecycle, signal, Span } from "@implementjs/core";
import { Progress } from "@/lib/components/ui/progress";
export default function ProgressDemo() {
const value = signal<number | null>(13);
return Div(
{ class: "flex w-full max-w-sm flex-col gap-2" },
ImplementLifecycle({
onMount: () => {
const timer = setInterval(() => {
value.update((v) => (v === null || v >= 100 ? 13 : Math.min(100, v + 29)));
}, 1000);
return () => clearInterval(timer);
},
}),
Div(
{ class: "flex items-center justify-between text-sm font-medium" },
Span({ id: "upload-label" }, "Uploading photos"),
Span(
{ class: "tabular-nums" },
value.bind((v) => `${v ?? 0}%`),
),
),
Progress({ "aria-labelledby": "upload-label", value }),
);
}A progress bar shows the completion status of a task — a file upload, an installation, a multi-step form. Progress renders a Div with role="progressbar" and the aria value attributes; you draw the track and the fill.
import { Progress } from "@implementjs/primitives";
Progress({ value: 40, "aria-label": "Uploading" });
It accepts optional props and children — pass a props object when you need attributes, or pass children directly. See createComponent. Extra props are forwarded onto the underlying Div.
A progress bar's value only ever advances toward completion. For a measurement that can move in either direction — CPU usage, battery level — use Meter instead; assistive technology announces the two differently.
Value and range
value defaults to 0 inside a range from min (0) to max (100). Pass numbers to seed them, or a signal to control them from outside (signal() returns a writable unchanged, so the same prop accepts both):
const uploaded = signal(0);
Progress({ value: uploaded, "aria-label": "Uploading" });
uploaded.set(40); // the aria and data attributes follow
Indeterminate
When you cannot know how far along the task is, pass null. The bar drops aria-valuenow, sets data-state="indeterminate", and adds a bare data-indeterminate attribute for styling:
Progress({ value: null, "aria-label": "Loading" });
A signal holding number | null can move between the two — start indeterminate while a size is unknown, then switch to real values.
Accessibility
The primitive sets role="progressbar", aria-valuemin, aria-valuemax, and aria-valuenow (omitted while indeterminate). Two things are left to you:
- A name. If there is a visible label, point
aria-labelledbyat itsid; otherwise passaria-label. - A readable value. Screen readers often announce
aria-valuenowas a percentage. When a percentage is not how a person would say the value, passaria-valuetext— an installer might use"step 2 of 5".
(Span({ id: "install-label" }, "Installing"),
Progress({
"aria-labelledby": "install-label",
"aria-valuetext": "step 2 of 5",
value: 2,
max: 5,
}));
Styling
The primitive is invisible until you style it — it has no default size or color. Style the root as the track and put your own fill inside it. data-value, data-min, and data-max are on the root for CSS to react to, and data-state moves through "loading", "loaded", and "indeterminate":
const uploaded = signal(40);
Progress(
{
value: uploaded,
"aria-label": "Uploading",
class: "h-2 w-56 overflow-hidden rounded-full bg-muted data-[state=loaded]:opacity-50",
},
Div({
class: "h-full bg-primary transition-[width]",
style: { width: uploaded.bind((v) => `${v}%`) },
}),
);
data-indeterminate is present only while the value is null, so one selector can swap the fill for a looping animation: data-indeterminate:animate-pulse on the root, or group-data-[indeterminate]:animate-slide on the fill.
API Reference
Progress
Completion status of a task. Sets role="progressbar" and the aria value attributes. Give it a track and a fill; it handles the semantics. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Signal<number | null> | number | null | 0 | The current value; null renders an indeterminate bar. Pass a signal to control it from outside; a number seeds uncontrolled state. |
min | Signal<number> | number | 0 | The value the bar starts from. |
max | Signal<number> | number | 100 | The value at which the task is complete. |
| Data attribute | Value |
|---|---|
[data-progress-root] | Present |
[data-state] | "loading" | "loaded" | "indeterminate" |
[data-value] | The current value; absent while indeterminate |
[data-min] | The minimum value |
[data-max] | The maximum value |
[data-indeterminate] | Present while the value is null |