Progress
Continuous progress — budget consumption, import jobs, storage quotas. A styled
native <progress> element (the platform's
value/max semantics and implicit progressbar role come free — zero
JS, zero ARIA). Distinct from Stepper, which is
for discrete named steps. Always pair it with visible text ("71% consumed") —
the fill is one channel.
Not for work of unknown duration — a bar that has no
honest value either sits frozen or invents motion, and
both tell the reader something false about how far along they are. When
the server cannot say how much is left, use a
loading state and say
what is happening in words; save the bar for the case where a real
fraction exists. A determinate-looking bar on indeterminate work is the
single most common misuse of this element.
Basic
View code
<progress class="bo-progress" max="100" value="71" aria-label="71% of budget consumed"></progress>
<span class="bo-u-text-muted">71% consumed</span> Born from a real dogfood finding: unstyled native
<progress> renders platform-blue chrome that ignores the
theme in both light and dark — this class is the token-true re-skin.
Tones — threshold states
83% consumed — approaching budget
97% consumed — review before approving
View code
<progress class="bo-progress" max="100" value="42"></progress>
<span class="bo-u-text-muted">42% consumed</span><br>
<progress class="bo-progress bo-progress--warning" max="100" value="83"></progress>
<span class="bo-u-text-muted">83% consumed — approaching budget</span><br>
<progress class="bo-progress bo-progress--danger" max="100" value="97"></progress>
<span class="bo-u-text-muted">97% consumed — review before approving</span> --warning / --danger for
approaching/exceeded thresholds. The tone is decoration on top of the text —
the accompanying text must say it ("approaching budget"), never the color
alone. Fill-on-track contrast for all three tones is gate-checked at 3:1
(WCAG 1.4.11) in both themes.
On a dashboard card
The dashboard pairing: a stat tile carries the number, the bar makes the trend scannable at a glance across a grid of cost centers.
Markup
<!-- A real <progress> element — value/max semantics and the progressbar
role come from the platform; the class only re-skins it. ALWAYS pair
with visible text: the fill and its tone are one channel each. -->
<progress class="bo-progress" max="100" value="71"></progress>
<span class="bo-u-text-muted">71% consumed</span> Quick reference generated
| Class | Role |
|---|---|
| .bo-progress | block (root) |
| .bo-progress--danger | variant: danger |
| .bo-progress--warning | variant: warning |
API generated from CSS
| Blocks | bo-progress |
|---|---|
| Variants | bo-progress--dangerbo-progress--warning |
| JS required | None — CSS-only. |
- A native
<progress>element, not a div —value/maxand theprogressbarrole are platform semantics; the framework only styles it. Give it an accessible name (aria-labelor a<label>) — adjacent prose text is NOT programmatically associated, and the name can carry the real value when you visually cap the bar ("112% of budget consumed" withvalue="100"). - Always pair with visible text ("71% consumed") — the fill is one channel, and tones are decoration on top of the text, never a substitute for it.
- Under forced-colors the element reverts to platform rendering (
appearance: auto) — custom fills would otherwise vanish entirely. - Always set
value— indeterminate (value-less) progress is NOT supported by this styling (appearance: nonesuppresses the platform's indeterminate animation, so "unknown" would render as an empty bar). For unknown-duration work, use Skeleton instead. If a determinate flow LOSES its value mid-stream (an import stalls, the server stops reporting), keep the bar at its last value and let the paired text say so ("stalled at 93%") — don't swap components mid-flow. - For discrete named steps (Submitted → Approved → Paid), use Stepper instead.
Contrast — 13 AA pairs this component uses (generated, CI-gated)
| Text | on | Light | Dark |
|---|---|---|---|
| text-secondary | bg-muted | 9.37 | 9.75 |
| text-muted | bg-muted | 6.87 | 5.66 |
| accent | bg-surface | 5.47 | 9.07 |
| accent | bg-selected | 5.25 | 6.66 |
| accent | accent-subtle | 5.25 | 6.66 |
| border-control | bg-muted | 4.39 | 5.66 |
| accent | bg-muted | 4.97 | 7.72 |
| warning-strong | bg-muted | 4.56 | 6.69 |
| warning-strong | bg-surface | 5.02 | 7.86 |
| danger | bg-muted | 4.39 | 5.19 |
| text-primary | bg-muted | 16.12 | 13.75 |
| success-text | bg-muted | 4.56 | 10.23 |
| danger-text | bg-muted | 5.88 | 7.57 |
Maturity generated
- Introduced
- v0.1.0
- CSS floor
- Chrome/Edge 99 · Firefox 97 · Safari 15.4
- Alignment scored
- 2026-08-23
- Assistive-tech evidence
- None recorded
Pin this component's first release with npm i @busy-office/ui@0.1.0.
The floor is derived from this component's stylesheet alone — set by
@layer — so it sits at or below the
whole framework's Chrome/Edge 119 · Firefox 129 · Safari 17.5.
No assistive-technology runtime result is recorded for any component: it needs a human listening to VoiceOver and NVDA on owner hardware; no gate in this repo can simulate announcement behaviour.
What automation covers instead is test:axe across every page at 1440 and 390, and check:forced-colors — see
accessibility.
Design-system alignment generated
100% (15/15) — how closely this component follows the design language. Each rule is scored 0-3 against a checkable citation; rules that don't apply leave the denominator. Scored 2026-08-23.
| Rule | Score | Evidence |
|---|---|---|
| Typography | 3 / 3 | no font-size at all — it renders no text of its own |
| Colour | 3 / 3 | accent/warning/danger tokens across both engine pseudo-elements; forced-colors hands the element back to the platform with appearance:auto |
| Spacing | 3 / 3 | the 10rem x 0.5rem bar proportions state why they are intrinsic (94.6b) |
| Interaction | n/a | a native <progress>; value/max semantics and the progressbar role come from the platform, and it is not interactive |
| Content | 3 / 3 | opener carries the wrong-choice clause: "Not for work of unknown duration" — a bar with no honest value either freezes or invents motion, and both misstate progress; points at a loading state plus words. Blind re-scored 2026-08-23 (Polish round 10). The scorer flagged a near-miss and resolved it: the ApiTable's advice to freeze a stalled bar at 93% is consistent, because 93% completed IS a real fraction and that note scopes itself to flows that started determinate. |
| Fit | 3 / 3 | continuous progress; the source names .bo-stepper as the discrete-step alternative |