Progress comes in two shapes sharing one palette: a bar built on the native <progress> element, and an SVG ring for app surfaces. Both indicate completion percentage and support the same status colour variants.
Basic usage
<progress class="progress-bar" value="60" max="100">60%</progress>
The fallback text (60%) is shown in browsers that do not support <progress>.
Status variants
<progress class="progress-bar progress-bar--success" value="100" max="100">100%</progress>
<progress class="progress-bar progress-bar--warning" value="40" max="100">40%</progress>
<progress class="progress-bar progress-bar--danger" value="10" max="100">10%</progress>
Progress ring
is the bar's job in a compact, centre-labelled circle — for app surfaces where a full-width bar has no width to fill: a completion figure beside a heading, a step counter in a sheet. Determinate only, like the bar.
<div class="progress-ring" role="progressbar"
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
aria-label="Profile completeness"
style="--progress-ring-value: 60">
<svg viewBox="0 0 48 48" fill="none" width="100%" height="100%" aria-hidden="true">
<circle class="progress-ring-track" cx="24" cy="24" r="20" pathLength="100"/>
<circle class="progress-ring-fill" cx="24" cy="24" r="20" pathLength="100"/>
</svg>
<span class="progress-ring-label" aria-hidden="true">60%</span>
</div>
The value is fed twice, and the two must move together: --progress-ring-value (a unitless 0–100 number) draws the arc, aria-valuenow announces it — the same pairing rule as and aria-pressed on the segmented control. From script:
ring.style.setProperty('--progress-ring-value', value);
ring.setAttribute('aria-valuenow', String(value));
label.textContent = value + '%';
The circles carry pathLength="100", so the dash arithmetic is percentage arithmetic — keep that attribute and the CSS never needs to know the radius. The canonical drawing is viewBox="0 0 48 48" with r="20"; rendered at --progress-ring-size the whole drawing scales together, so a bigger ring is a bigger drawing, not a thinner one.
Status variants use the same grammar as the bar: , , .
The centre label is optional; drop the <span> for a bare ring. Keep aria-hidden on it when present — aria-valuenow already announces the number, and without it a screen reader hears the value twice.
Accessibility
- The native
<progress>element is announced by screen readers automatically - Always include fallback text content inside the element
- If the progress represents a named process, associate it with a label using
aria-labelor a visible<label> - The ring is a styled
<div>, so its semantics are declared:role="progressbar",aria-valuenow,aria-valuemin,aria-valuemax, and anaria-labelnaming the process - Under reduced motion both shapes still move to the new value — they get there without the sweep
- In forced-colors mode the ring declares its own pair (
GrayTexttrack,Highlightfill): the mode would flatten the token strokes, and author-declared system colour keywords are honoured where token values are not
Usage rules
Do:
- Use status variants to indicate outcome (green for complete, red for critical)
- Pair a status ring with visible text naming the outcome — the hue adds meaning it must never carry alone
- Include fallback text inside the
<progress>element - Update
--progress-ring-valueandaria-valuenowin the same place, always
Don't:
- Don't use progress bars or rings for indeterminate loading, use a skeleton instead
- Don't animate the value attribute with JS unless the operation is genuinely progressing
- Don't rebuild the ring at another viewBox — resize it with
--progress-ring-size
CSS reference
This section documents how the component is built. For usage, see the sections above.
Tokens
| Token | Default | Purpose |
|---|---|---|
| Track (unfilled) colour, bar and ring | ||
| Fill (completed) colour, bar and ring | ||
0px |
Bar corner radius | |
| Bar height | ||
| Value-change travel, bar and ring — one knob times both shapes | ||
48px |
Ring rendered diameter | |
4px |
Stroke width, in viewBox units — scales with the ring |
Selectors
| Selector | Purpose |
|---|---|
progress.progress-bar |
Base component: width, height, border-radius, appearance reset |
progress.progress-bar::-webkit-progress-bar |
Track background (WebKit) |
progress.progress-bar::-webkit-progress-value |
Fill colour (WebKit) |
progress.progress-bar::-moz-progress-bar |
Fill colour (Firefox) |
progress.progress-bar--success |
Success status variant, green fill |
progress.progress-bar--warning |
Warning status variant, yellow fill |
progress.progress-bar--danger |
Danger status variant, red fill |
| Ring container: size, centre-label grid, default | |
.progress-ring svg |
The drawing, rotated so progress runs from 12 o'clock |
| Track circle | |
| Fill circle: dash arithmetic, rounded cap, sweep transition | |
| Centre label | |
/ --warning / --danger |
Status variants, same grammar as the bar |
Use in another product
The design system installs once per product:
npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0
This component's styles ship in design-system.css. No JavaScript, nothing else to include.