ProgressRing draws a determinate value as an arc around a full circular track. Reach for it when a circle reads better than a bar — a compact upload badge, a countdown next to an icon, a status glyph in a tight corner of a screen. The arc starts at the top of the circle and sweeps clockwise.size variant, so it never reflows the layout around it as the value changes. Use CircularProgressBar instead when you need a partial arc, a caller-controlled stroke weight, or a dial that grows with its parent; use ProgressIndicator when a horizontal bar reads more clearly; use IndeterminateLoader when there is no value to show.import { ProgressRing, ProgressRingSize } from '@wearables-ui-toolkit/mrbd';
type Props = {
uploaded: number;
total: number;
};
export function UploadStatus({ uploaded, total }: Props) {
return (
<div className="upload-status">
<ProgressRing
progress={total === 0 ? 0 : uploaded / total}
size={ProgressRingSize.LARGE}
animated
announceUpdatesForAccessibility
aria-label="Uploading photos"
/>
<span className="uit-text-body2">Uploading photos</span>
</div>
);
}
.upload-status {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--uit-spacing-small);
}
import { IconImage, ProgressRing } from '@wearables-ui-toolkit/mrbd';
import type { IconSource } from '@wearables-ui-toolkit/mrbd';
type Props = {
progress: number;
icon: IconSource;
};
export function RingWithGlyph({ progress, icon }: Props) {
return (
<div className="ring-slot">
<ProgressRing progress={progress} aria-label="Sync" />
<IconImage source={icon} />
</div>
);
}
.ring-slot {
display: grid;
place-items: center;
}
.ring-slot > * {
grid-area: 1 / 1;
}
ProgressRingProps, which extends HTMLAttributes<HTMLDivElement>.| Prop | Type | Default | Description |
|---|---|---|---|
progress | number | 0 | Fraction complete from 0 to 1. Values outside the range are clamped. |
size | ProgressRingSize | ProgressRingSize.SMALL | Fixed square footprint of the ring. |
animated | boolean | false | Tween arc changes with the progress spring instead of snapping. Applies to every change while set. |
announceUpdatesForAccessibility | boolean | false | Publish the rounded percentage into a polite live region so screen readers narrate each change. |
aria-label | string | — | Appended after the percentage in the accessible name. Blank or whitespace-only labels are ignored. |
className | string | '' | Added to the outer wrapper. |
style | CSSProperties | {} | Applied to the outer wrapper. |
progressbar element, not to the outer wrapper. Other DOM props you pass — aria-*, data-*, and the rest — are applied to that same progressbar element, while className and style stay on the outer wrapper. The component writes role, aria-valuenow, aria-valuemin, and aria-valuemax last, so values you pass for those four are overwritten.ProgressRingSize| Member | Description |
|---|---|
SMALL | Compact ring for inline and accessory placements. |
LARGE | Prominent ring for a screen’s primary status. |
progress is a normalized fraction, not a percentage and not a range: divide your own values before passing them, and guard against dividing by zero. Anything below 0 or above 1 is clamped.progress={0}, drawn with zero length, so the ring does not restructure its markup as the value crosses zero.--uit-color-background-track and the arc uses --uit-color-control-active, which keeps both legible against the additive-light display.animated applies a sampled spring transition — the same SpringConfigs.PROGRESS_VALUE curve used across determinate progress — to the arc. It is a persistent declarative prop backed by a CSS transition, so it affects every subsequent change while it is set. To animate only some updates, toggle it around the update.usePrefersReducedMotion and drop animated if your app honors a reduced-motion preference.role="progressbar", aria-valuenow set to the rounded percentage, aria-valuemin of 0, and aria-valuemax of 100. Note that aria-valuenow is the percentage, not the 0-to-1 fraction you passed.aria-label when you supply one — 42%, Uploading photos. A label that is empty or only whitespace is ignored.announceUpdatesForAccessibility on publishes just the percentage into a polite, atomic live region that sits outside the progressbar element so the change is reliably picked up. Your label is part of the static accessible name, so it is not repeated on every update.aria-* props land on the progressbar element, aria-labelledby and aria-describedby work as expected for pointing at on-screen text.0-to-1 range at the edge of your state, not inside the render path.ProgressRing does not accept content for the center.className and style to the outer wrapper as documented; they do not reach the progressbar element.