busy-office-ui
Menu

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

71% consumed
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

42% consumed
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

Budget — CC-4021
consumed 93%

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

ClassRole
.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/max and the progressbar role are platform semantics; the framework only styles it. Give it an accessible name (aria-label or 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" with value="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: none suppresses 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)
TextonLightDark
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
↑↓ move Enter open Esc close