ComponentsMisc
Progress circle
A ring that fills to show how far a task has got, or turns while work of unknown length runs, where a bar has no room.
Import
import { ProgressCircle } from "@booleanpress/ui/progress-circle"Usage
Pass value, a number from 0 to 100, and a name. The name is required: aria-label, or aria-labelledby pointing at visible text.
import { ProgressCircle } from "@booleanpress/ui/progress-circle"
export function StorageUsed() {
return <ProgressCircle value={75} showValue aria-label="Storage used" />
}It is controlled only, like Progress: it shows the value you pass. The ring starts at the top and fills clockwise (counter-clockwise on a right-to-left page). max changes the top of the range.
No value makes it indeterminate: a quarter of the ring turns, and aria-valuenow is left out. For a busy state inside a button or a field, the smaller Spinner is the better fit.
The value inside. showValue writes the value in the middle, as a percentage in the provider's locale; getValueLabel replaces the text, and is the ring's aria-valuetext too. It is not drawn at sm.
Sizes and colours. size is sm (24 px), default (40 px) or lg (64 px), with a 3, 4 or 6 px ring; strokeWidth sets another thickness. variant colours the filled arc: default (the primary colour), success, info, warning or destructive. Say what the colour means in text as well.
Examples
Basic
A ring at 75 %, named with aria-label.
Indeterminate
No value: a quarter of the ring turns, named by the text beside it.
import { ProgressCircle } from "@booleanpress/ui/progress-circle"
export default function ProgressCircleIndeterminate() {
return (
<div className="flex items-center gap-3">
<ProgressCircle size="sm" aria-labelledby="checking-dns" />
<span id="checking-dns" className="text-sm">
Checking the DNS records…
</span>
</div>
)
}Sizes
size sm, default and lg: 24, 40 and 64 px.
import { ProgressCircle } from "@booleanpress/ui/progress-circle"
export default function ProgressCircleSizes() {
return (
<div className="flex items-center gap-6">
<ProgressCircle size="sm" value={40} aria-label="Small, 40 percent" />
<ProgressCircle value={60} aria-label="Default, 60 percent" />
<ProgressCircle size="lg" value={80} aria-label="Large, 80 percent" />
</div>
)
}With a label
showValue writes the percentage in the middle; the large ring is named by the text beside it.
import { ProgressCircle } from "@booleanpress/ui/progress-circle"
export default function ProgressCircleWithLabel() {
return (
<div className="flex items-center gap-6">
<ProgressCircle value={75} showValue aria-label="Monthly sending quota" />
<div className="flex items-center gap-3">
<ProgressCircle size="lg" value={42} showValue aria-labelledby="quota-label" />
<div className="text-sm">
<p id="quota-label" className="font-medium">
Monthly sending quota
</p>
<p className="text-muted-foreground">4,200 of 10,000 emails</p>
</div>
</div>
</div>
)
}Colours
variant success, info, warning and destructive on the status colours, each named for what it counts.
import { ProgressCircle } from "@booleanpress/ui/progress-circle"
export default function ProgressCircleColours() {
return (
<div className="flex items-center gap-6">
<ProgressCircle size="lg" variant="success" value={98} showValue aria-label="Delivered" />
<ProgressCircle size="lg" variant="info" value={64} showValue aria-label="Opened" />
<ProgressCircle size="lg" variant="warning" value={12} showValue aria-label="Deferred" />
<ProgressCircle size="lg" variant="destructive" value={2} showValue aria-label="Bounced" />
</div>
)
}Accessibility
- Semantics
- Radix renders a
divwithrole="progressbar",aria-valuemin="0",aria-valuemax,aria-valuenowandaria-valuetext(the percentage in the provider's locale, orgetValueLabel's text), plusdata-state(indeterminate,loading,complete). With no value,aria-valuenowandaria-valuetextare left out. The ring and the text in it arearia-hidden. - Labels
- A name is required, and the types ask for it:
aria-label, oraria-labelledbypointing at visible text. Name what is measured ("Monthly sending quota"), not the control ("Progress"). - Focus
- A progress circle takes no focus and has no keyboard behaviour, so it has no keyboard rows.
- Known limits
- It is not a live region: screen readers do not announce each change. Announce milestones in a live region the page owns.
- The ring alone is not a precise reading, and its colour carries no meaning by itself: put the number or the state in text when it matters.
- The turn of the indeterminate ring and the slide to a new value end under
prefers-reduced-motion(theme.css): the ring rests with its quarter arc at the top.
Keyboard
| Key | Behaviour |
|---|
API
ProgressCircle
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | ||
getValueLabel | ((value: number, max: number) => string) | Returns the value's text from the value and the max: the ring's aria-valuetext, and the text in the middle with showValue. A percentage in the provider's locale by default. | |
max | number | The top of the range. 100 by default, and 100 when it is not a positive number. | |
showValue | boolean | false | Writes the value in the middle: getValueLabel's text, a percentage by default. Not drawn at sm. |
size | "default" | "sm" | "lg" | default | The ring's size: sm 24 px, default 40 px, lg 64 px. |
strokeWidth | number | The ring's thickness in pixels at its own size. 3, 4 and 6 px for sm, default and lg. | |
value | number | null | The progress from 0 to max; a value outside the range is clamped to it. Leave it out for the indeterminate ring. | |
variant | "default" | "destructive" | "success" | "info" | "warning" | default | The colour of the filled arc: the primary colour, or a status colour. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="progress-circle" (ProgressCircle), and data-size, data-variant.
Theming
The track is --border; the filled arc is the text colour, --primary by default or --success, --info, --warning and --destructive; the value in the middle is --foreground, 10 px semibold (12 px medium at lg). Set another arc colour with a text-* class, and another size with size-* (the ring scales with it).
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--border | stroke |
--destructive | text |
--foreground | text |
--info | text |
--primary | text |
--success | text |
--warning | text |