Progress
Shows how far along something is, as a proportion of a known whole.
When to Use
indeterminate mode when work is genuinely underway but unmeasurable.
Variants
Colour
Five variants. Colour carries meaning, so drive it from the state rather than the page's palette — success at 100%, error when something failed part-way.
<?= ws_progress(75, ['variant' => 'primary']) ?> <?= ws_progress(100, ['variant' => 'success']) ?>
Sizes
<?= ws_progress(60, ['size' => 'sm']) ?>
With label and value
The label doubles as the bar's accessible name, so set it whenever the surrounding text doesn't already explain what's progressing.
<?= ws_progress(65, [
'variant' => 'primary',
'label' => 'Uploading file…',
'showValue' => true,
]) ?>States
Striped and animated
animated requires striped — the animation moves the stripes, so on its own it does nothing visible.
<?= ws_progress(70, ['striped' => true, 'animated' => true]) ?>
Indeterminate
For work with no measurable proportion. The bar loops continuously, the width style is dropped, and the percentage is suppressed even if showValue is set.
<?= ws_progress(0, ['indeterminate' => true, 'label' => 'Loading…']) ?>
| State | Behavior |
|---|---|
| Value out of range | Clamped with max(0, min(100, $value)), so a stray 150 or −10 renders as 100 or 0 rather than overflowing the track. |
| Zero | Renders an empty track. Visually identical to "not started", which is usually what you want. |
| Complete | No automatic treatment at 100% — switch to variant: 'success' yourself if completion should read differently. |
| Indeterminate | Adds .ws-progress__bar--indeterminate and omits the inline width. aria-valuenow is also omitted, so no false percentage is announced. |
| Reduced motion | Honored. Under prefers-reduced-motion: reduce both loops stop; the indeterminate bar fills its track at reduced opacity so it still reads as in-progress. |
Real-World Usage
A workshop's setup completion on MyWorkshopr: value derived from real task state, label naming what's measured, and the variant flipping to success once everything's done.
<?php
$done = count(array_filter($tasks, fn($t) => $t['done']));
$pct = $tasks ? round($done / count($tasks) * 100) : 0;
?>
<?= ws_progress($pct, [
'variant' => $pct === 100 ? 'success' : 'primary',
'label' => 'Workshop setup',
'showValue' => true,
]) ?>Options
| Option | Type | Default | Purpose |
|---|---|---|---|
$value | int|float | 0 | Positional. 0–100, clamped to that range. |
variant | string | 'primary' | primary | success | warning | error | info |
size | string | 'md' | sm | md | lg — track height |
label | string | null | Visible label, also used as the bar's aria-label |
showValue | bool | false | Show the percentage. Suppressed when indeterminate. |
striped | bool | false | Diagonal stripe pattern |
animated | bool | false | Animates the stripes. Has no effect without striped. |
indeterminate | bool | false | Continuous loop for unmeasurable work |
id | string | null | Element ID |
class | string | '' | Additional CSS classes |
attrs | array | [] | Extra HTML attributes as key/value pairs |
Accessibility
| Concern | Behavior |
|---|---|
| ARIA | Emits role="progressbar" with aria-valuemin="0" and aria-valuemax="100". aria-valuenow is added for determinate bars and correctly omitted when indeterminate is set. |
| Labelling | When label is set it becomes aria-label. Without one the bar is announced as an unnamed progressbar, so pass a label unless neighbouring text clearly names it. |
| Keyboard | Not focusable, which is correct — a progress bar reports state and accepts no input. |
| Live updates | Updating aria-valuenow in the DOM is announced by most screen readers without a live region. Don't wrap the bar in aria-live="assertive" — that produces a stream of interruptions on every tick. |
| Colour independence | Variant colour is the only signal distinguishing error from success at the same percentage. Put the state in the label too. |
aria-valuenow was emitted even when indeterminate, so assistive tech announced a bogus percentage for work of unknown length; and neither animation honoured prefers-reduced-motion, unlike Skeleton. Both now behave correctly.
Tokens
| Token | Used for |
|---|---|
--gray-100 | Track background |
--mainsite-gradient | Primary bar fill |
--color-success / --color-success-dark | Success bar |
--color-warning / --color-warning-dark | Warning bar |
--color-error / --color-error-dark | Error bar |
--color-info / --color-info-dark | Info bar |
--radius-full | Rounded track and bar ends |
--text-dark / --text-default | Label and value text |
--text-meta | Label and value type scale |
--font-body / --font-medium / --font-semibold | Face and weights |
--transition-base | Width transition as the value changes |
CSS Classes
| Class | Purpose |
|---|---|
.ws-progress-wrapper | Outer wrapper holding the label row and the track |
.ws-progress | The track — carries role="progressbar" |
.ws-progress--sm / --md / --lg | Track height |
.ws-progress__label | Label text |
.ws-progress__value | Percentage readout |
.ws-progress__bar | The filled portion |
.ws-progress__bar--primary / --success / --warning / --error / --info | Bar colours |
.ws-progress__bar--striped | Stripe pattern |
.ws-progress__bar--animated | Moving stripes (needs --striped) |
.ws-progress__bar--indeterminate | Continuous loop |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_progress() helper function |
includes/components/progress.php | Template — clamping, ARIA, and class assembly |
includes/components/components.css | Styles and the stripe/indeterminate keyframes (.ws-progress rules) |