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.