CircularProgress
The same task status as Progress, drawn as a ring. It takes a fixed square instead of a full-width row, which is what makes it the shape for a button, a card corner, an avatar overlay — anywhere there is no line to span.
import { CircularProgress } from '@ontoui/react';
<CircularProgress.Root value={40} size={72}> <CircularProgress.Track> <CircularProgress.Indicator /> </CircularProgress.Track> <CircularProgress.Value /> <CircularProgress.Label>Uploading</CircularProgress.Label></CircularProgress.Root>;Anatomy
| Part | Description |
|---|---|
CircularProgress.Root | <div role="progressbar"> — holds the value and the geometry, and announces it. |
CircularProgress.Track | <svg> — the rail, and the canvas the indicator is drawn into. |
CircularProgress.Indicator | <circle> — the arc. Must sit inside the track. |
CircularProgress.Value | <span> — the value as text, centred in the ring. |
CircularProgress.Label | <span> — names the task. Becomes the ring’s accessible name. |
The value is centred over the ring and the label sits beneath it, whatever order the parts are written in — the two share a grid cell with the ring rather than being positioned against it.
Both the ring and the value are optional. A spinner is a root with a track and an indicator and nothing else.
Value
value is required, and it is always controlled. Intl.NumberFormat options go through
format, and min and max set the scale — all of it exactly as on
Progress, including the default reading of the value as a
percentage of 100.
<CircularProgress.Root value={3} max={5} format={{ style: 'decimal' }}> <CircularProgress.Track> <CircularProgress.Indicator /> </CircularProgress.Track> <CircularProgress.Value>{(formatted) => `${formatted}/5`}</CircularProgress.Value></CircularProgress.Root>The arc starts at twelve o’clock and grows clockwise, and it is stroked from the value against
min and max — so the ring is at a quarter turn when the value is a quarter of the way along,
whatever the text in the middle says.
Keep the centred text short. It is bounded by the ring’s inner diameter, and 40% or 3/5 fits
where 840 MB of 1,024 MB does not — that belongs in a label beside the ring.
Indeterminate
value={null} says the task is running and its progress is unknown: the ring drops
aria-valuenow, announces itself as indeterminate, and spins.
<CircularProgress.Root value={null} size={20} thickness={3}> <CircularProgress.Track> <CircularProgress.Indicator /> </CircularProgress.Track></CircularProgress.Root>This is the library’s spinner. A determinate ring is better whenever the work can be measured — a spinner says nothing about whether it is nearly done or has barely started.
Size
<CircularProgress.Root value={65} size={96} thickness={10}> <CircularProgress.Track> <CircularProgress.Indicator /> </CircularProgress.Track> <CircularProgress.Value /></CircularProgress.Root>size is the diameter in pixels and thickness is the stroke, both props rather than CSS
because the SVG geometry is computed from them: the circle is inset by half a stroke so the ring
is not clipped by its own box, and the arc length is derived from the radius that leaves.
size defaults to 40 and thickness to 4. The centred value scales with the ring, so it
stays in proportion from an inline 24px spinner up to a 96px card graphic — but a ring much
below 40px has no room for text inside it, so give small ones a label instead.
Color
<CircularProgress.Root value={88} color="warning">Color is decorative — a screen reader hears the same value at any tint. Keep the meaning in the label rather than in the color alone.
Accessibility
CircularProgress.Labelnames the ring. Without it, giveCircularProgress.Rootanaria-label— this matters most for the spinner, which otherwise announces only that something unnamed is in progress.- The ring itself is
aria-hidden. It is a drawing of the value the root already announces. CircularProgress.Valueis hidden from screen readers for the same reason.- Motion is reduced, not removed. Under
prefers-reduced-motion: reducethe spinner stops turning and a quarter arc pulses in place — a still, empty ring would read as stalled.
API Reference
CircularProgress.Root
| Prop | Type | Default |
|---|---|---|
children? | ReactNode | — |
className? | string | — |
style? | CSSProperties | — |
value | number | null | — |
min? | number | 0 |
max? | number | 100 |
size? | number | 40 |
thickness? | number | 4 |
format? | NumberFormatOptions | — |
locale? | LocalesArgument | the runtime locale |
getAriaValueText? | (formattedValue: string | null, value: number | null) => string | — |
color? | "default" | "success" | "warning" | "danger" | 'default' |
ref? | Ref<HTMLDivElement> | — |
CircularProgress.Track
| Prop | Type | Default |
|---|---|---|
children? | ReactNode | — |
className? | string | — |
style? | CSSProperties | — |
ref? | Ref<HTMLDivElement> | — |
CircularProgress.Indicator
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
ref? | Ref<HTMLDivElement> | — |
CircularProgress.Value
| Prop | Type | Default |
|---|---|---|
className? | string | — |
style? | CSSProperties | — |
children? | (formattedValue: string | null, value: number | null) => ReactNode | — |
ref? | Ref<HTMLDivElement> | — |
CircularProgress.Label
| Prop | Type | Default |
|---|---|---|
children? | ReactNode | — |
className? | string | — |
style? | CSSProperties | — |
id? | string | — |
ref? | Ref<HTMLDivElement> | — |