Progress
A progress indicator that supports determinate, indeterminate, and complete states. Manages ARIA attributes, formats values via Intl.NumberFormat, and synchronizes state across all slot elements.
import { Progress } from "@areia/slots";
document.querySelector("#root")!.innerHTML = `
<div data-slot="progress" data-value="60" class="w-72">
<span data-slot="progress-label" class="mb-1 block font-bold">Uploading files</span>
<div data-slot="progress-track" class="win95-inset h-5 bg-white">
<div data-slot="progress-indicator" class="h-full bg-blue-900"></div>
</div>
<span data-slot="progress-value" class="mt-1 block text-xs"></span>
</div>
`;
const root = document.querySelector('[data-slot="progress"]')!;
const controller = Progress.createProgress(root, {
value: 60,
min: 0,
max: 100,
});Import
import { Progress } from "@areia/slots";Usage
Call createProgress with the root element and optional configuration. The primitive reads slot attributes from the DOM, computes percentage and status, and synchronizes ARIA attributes across all parts.
import { Progress } from "@areia/slots";
const root = document.querySelector<HTMLElement>(
'[data-slot="progress"]',
)!;
if (!root) throw new Error("No root element found");
const controller = Progress.createProgress(root, {
value: 75,
min: 0,
max: 100,
});
// Programmatic control
controller.setValue(50);
// controller.set({ value: 25, max: 200 });
// controller.destroy();Auto-bind
Use the create helper to discover and bind every unattached root in a scope.
import { Progress } from "@areia/slots";
const controllers = Progress.create(document);
// controllers.forEach((c) => { ... });Expected Markup
The primitive expects the following slot structure. Only the root progress slot is required; all sub-slots are optional.
<div data-slot="progress" data-value="60">
<span data-slot="progress-label">Uploading files</span>
<div data-slot="progress-track">
<div data-slot="progress-indicator"></div>
</div>
<span data-slot="progress-value"></span>
</div>Indeterminate Progress
Set value to null (or omit it) to render indeterminate progress:
<div data-slot="progress">
<span data-slot="progress-label">Loading…</span>
<div data-slot="progress-track">
<div data-slot="progress-indicator"></div>
</div>
</div>Data Slots
| Slot | Required | Description |
|---|---|---|
progress |
Yes | Root element. Receives role="progressbar" and ARIA attrs. |
progress-label |
No | Accessible label linked via aria-labelledby. |
progress-track |
No | Visual track container. |
progress-indicator |
No | Visual fill bar. Width is set to {percent}% for determinate progress. |
progress-value |
No | Text element that displays the formatted value. |
Generated Attributes
The controller manages these attributes on the root and all sub-slot elements:
| Attribute | Element(s) | Values | Description |
|---|---|---|---|
data-state |
Root, all parts | "indeterminate" / "progressing" / "complete" |
Current progress status. |
data-indeterminate |
Root, all parts | (present) | Present when there is no value. |
data-progressing |
Root, all parts | (present) | Present when progress is underway. |
data-complete |
Root, all parts | (present) | Present when value has reached the maximum. |
data-value |
Root | string |
Current numeric value. |
data-min |
Root | string |
Minimum value. |
data-max |
Root | string |
Maximum value. |
data-percent |
Root, indicator | string |
Computed percentage (0–100). Absent when indeterminate. |
role |
Root | "progressbar" |
ARIA progressbar role. |
aria-valuemin |
Root | string |
Minimum value. |
aria-valuemax |
Root | string |
Maximum value. |
aria-valuenow |
Root | string |
Current value. Removed when indeterminate. |
aria-valuetext |
Root | string |
Human-readable value text. |
aria-labelledby |
Root | ID of label | Set when a progress-label slot is present. |
aria-hidden |
Track, value | "true" |
Present on decorative track and value elements. |
style.width |
Indicator | {percent}% |
Inline width set for determinate progress. |
style.inset-inline-start |
Indicator | 0px |
Inline start set for determinate progress. |
style.height |
Indicator | inherit |
Inline height set for determinate progress. |
Events
All events are dispatched from the root element (data-slot="progress").
Outbound
| Event | Detail | Description |
|---|---|---|
progress:value-change |
ProgressValueChangeDetail |
Fires when the value changes. |
progress:value-change
interface ProgressValueChangeDetail {
value: number | null;
previousValue: number | null;
percent: number | null;
status: "indeterminate" | "progressing" | "complete";
}| Field | Description |
|---|---|
value |
The new value (or null if indeterminate). |
previousValue |
The previous value before the change. |
percent |
Computed percentage (or null if indeterminate). |
status |
The resolved progress status. |
Inbound
| Event | Detail | Description |
|---|---|---|
progress:set |
number | null | ProgressSetDetail |
Set the value from outside the controller. |
Dispatch progress:set with a number (or null for indeterminate) to update the value:
const root = document.querySelector<HTMLElement>(
'[data-slot="progress"]',
)!;
// Set a value directly
root?.dispatchEvent(
new CustomEvent("progress:set", {
bubbles: true,
detail: 80,
}),
);
// Or pass a detail object to set value, min, and max
root?.dispatchEvent(
new CustomEvent("progress:set", {
bubbles: true,
detail: { value: 50, min: 0, max: 200 },
}),
);API Reference
Options
Progress.createProgress(root, options)
| Option | Type | Default | Description |
|---|---|---|---|
value |
number | null |
null |
Current value. null or undefined means indeterminate. |
min |
number |
0 |
Minimum value. If max < min, they are swapped automatically. |
max |
number |
100 |
Maximum value. |
locale |
Intl.LocalesArgument |
— | Locale used by Intl.NumberFormat when rendering the value text. |
format |
Intl.NumberFormatOptions |
— | Number formatting options for the value slot. |
getAriaValueText |
(formattedValue: string | null, value: number | null) => string |
Default formatter | Custom function to produce the aria-valuetext string. |
onValueChange |
(value: number | null) => void |
— | Called when the value changes. |
Options can also be set via data-* attributes on the root element (e.g. data-value="60", data-min="0", data-locale="en-US"). JS options take precedence over data attributes.
Controller
| Member | Type | Description |
|---|---|---|
value |
number | null (getter) |
Current value. |
min |
number (getter) |
Current minimum value. |
max |
number (getter) |
Current maximum value. |
status |
"indeterminate" | "progressing" | "complete" (getter) |
Current progress status. |
percent |
number | null (getter) |
Computed percentage (0–100), or null if indeterminate. |
setValue(value) |
(value: number | null) => void |
Set the value. |
set(detail) |
(detail: ProgressSetDetail) => void |
Set value, min, and/or max in one call. |
destroy() |
() => void |
Remove all event listeners and clean up. |
ProgressSetDetail
interface ProgressSetDetail {
value?: number | null;
min?: number;
max?: number;
}Controller
The controller is the imperative handle returned by createProgress. Use it for programmatic control when you need to read or update progress from application logic.
import { Progress } from "@areia/slots";
const root = document.querySelector<HTMLElement>(
'[data-slot="progress"]',
)!;
if (!root) throw new Error("No root element");
const ctrl = Progress.createProgress(root, {
value: 0,
min: 0,
max: 100,
});
// Read state
console.log(ctrl.value); // 0
console.log(ctrl.percent); // 0
console.log(ctrl.status); // "progressing"
// Update value
ctrl.setValue(50);
console.log(ctrl.percent); // 50
// Update multiple properties at once
ctrl.set({ value: 100, min: 0, max: 200 });
console.log(ctrl.status); // "complete"
// Switch to indeterminate
ctrl.setValue(null);
console.log(ctrl.status); // "indeterminate"
// Tear down all listeners and clean up
ctrl.destroy();Behavior
- Status derivation:
indeterminatewhenvalueisnull;completewhenvalue >= max;progressingotherwise. Status attributes are applied to the root and every slot element for consistent CSS styling. - Value clamping: The value is clamped between
minandmax. Ifmax < min, they are swapped. - Percentage computation:
((value - min) / (max - min)) * 100, clamped to 0–100. Returnsnullwhen indeterminate. - Indicator sizing: For determinate progress, the indicator receives
style.width = "{percent}%",style.insetInlineStart = "0px", andstyle.height = "inherit". These are removed when indeterminate. - Value formatting: The
progress-valueslot is populated with the result ofIntl.NumberFormat(locale, format).format(value). Setlocaleandformatoptions to customize the rendered text. - ARIA: The root receives
role="progressbar"witharia-valuemin,aria-valuemax,aria-valuenow(removed when indeterminate), andaria-valuetext. The label slot is linked viaaria-labelledby. - Deduplication: Calling
Progress.createProgress()more than once on the same root returns the existing controller. Destroy it first if you need to rebind with new options. - Auto-bind: The
create()function discovers all[data-slot="progress"]elements in a scope and binds any that are not already bound.