Skip to content

Progress

Displays task completion progress with accessible progressbar semantics.

Updated View as Markdown

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: indeterminate when value is null; complete when value >= max; progressing otherwise. Status attributes are applied to the root and every slot element for consistent CSS styling.
  • Value clamping: The value is clamped between min and max. If max < min, they are swapped.
  • Percentage computation: ((value - min) / (max - min)) * 100, clamped to 0–100. Returns null when indeterminate.
  • Indicator sizing: For determinate progress, the indicator receives style.width = "{percent}%", style.insetInlineStart = "0px", and style.height = "inherit". These are removed when indeterminate.
  • Value formatting: The progress-value slot is populated with the result of Intl.NumberFormat(locale, format).format(value). Set locale and format options to customize the rendered text.
  • ARIA: The root receives role="progressbar" with aria-valuemin, aria-valuemax, aria-valuenow (removed when indeterminate), and aria-valuetext. The label slot is linked via aria-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.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close