Progress ring.
A progress ring is the circular counterpart to the progress bar — the same status, in a compact footprint that fits inside a card, a dashboard tile or a button. Use determinate whenever a percentage is knowable; indeterminate spins continuously for a duration that genuinely can't be predicted.
Colour
Size
Two sizes, matching the Figma Size variant (100 / 200px). Track and indicator width stay a fixed 4px at both sizes.
Value label
The Figma component's Show value boolean — an optional centred percentage. Omit it when the number already appears beside the ring.
State
Determinate shows a real value. Indeterminate — not part of the Figma component — is a token-built addition for when the duration genuinely can't be known.
Anatomy
Do & don't
Accessibility
role="progressbar"On the wrapping element, not the SVG — screen readers announce the role once, on the container that owns the value.aria-valuenow / min / maxDeterminate only. Omit all three on an indeterminate ring rather than reporting a number that doesn't exist.aria-label / aria-labelledbySay what is progressing — "Report generating" — not just "Progress". The ring carries no text of its own unless Show value is on.ColourNever the only cue, same rule as Progress bar, Alert and Badge — pair a status colour with the label or surrounding copy.Reduced motionThe indeterminate spin honours prefers-reduced-motion and the in-app Motion: reduced setting — the arc freezes in place rather than rotating, same mechanism as the skeleton loaders and the indeterminate progress bar.Live updatesA determinate ring whose value changes while off-screen should sit in an aria-live="polite" region.Props (React)
No primitive ships yet — this is the target API if one is built, not something you can import today.
type ProgressRingProps = {
value?: number; // current amount
max?: number; // default 100
color?: "primary" | "neutral" | "info" | "success" | "warning" | "danger";
size?: 100 | 200; // small or large
trackWidth?: number; // default 4
showValue?: boolean; // renders a centred "NN%" label
indeterminate?: boolean; // spins continuously; value/max are ignored
label: string; // required — feeds aria-label
};