Ring Progress
betaA small ring that fills toward a goal, with the percent inside. It turns green once the goal is met.
Preview
tone
no tone
size
no value
beside words
in a list
- Ava Clarke9 of 12
- Sam Okafor12 of 12
- Mia Hughes4 of 10
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/ring-progress
Use it for
- Progress toward a goal in a small space, like tasks done this week, a profile that is 75% done, or a target in a card.
- Pass value as a percent, or value and max, like value={3} max={4}.
- Pass aria-label to say what it measures, like 'Tasks'. Screen readers hear 'Tasks: 75%'.
- Pass showValue={false} for a small ring beside its own words, like '3 of 4'.
- Leave tone out, and the ring turns success once it is full. Pass a tone to keep one color.
Avoid it for
- A task that runs, like an upload. Use Progress.
- Parts of a whole, like a budget split. Use DonutChart.
- A ranking or many values side by side. Use BreakdownList.
Props
- value
Required. How much is done, from 0 to max. Values outside the range are clamped.
number
- max
The value of a full ring.
number · default 100
- tone
Color of the filled part. Left out, the ring is neutral and turns success once it is full. Passed, it stays that tone.
"neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"
- size
36, 44, or 66px across at the default density. lg uses larger text.
"sm" | "md" | "lg" · default "md"
- showValue
Shows the percent inside. The percent is read out either way.
boolean · default true
- aria-label
What the ring measures, like 'Tasks'. Screen readers hear it before the percent.
string
- texture
dither draws the filled part as dots, gradient fades it along the arc to full at its end, and grain speckles it, with a glow on dark pages. Hover fills gradient and grain in. Left out, the ring follows the Texture around it.
"flat" | "dither" | "gradient" | "grain"
- className
Extra classes. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| value | number | - | Required. How much is done, from 0 to max. Values outside the range are clamped. |
| max | number | 100 | The value of a full ring. |
| tone | "neutral" | "brand" | "danger" | "success" | "warning" | "neutral" | Color of the filled part. Left out, the ring is neutral and turns success once it is full. Passed, it stays that tone. |
| size | "sm" | "md" | "lg" | "md" | 36, 44, or 66px across at the default density. lg uses larger text. |
| showValue | boolean | true | Shows the percent inside. The percent is read out either way. |
| aria-label | string | - | What the ring measures, like 'Tasks'. Screen readers hear it before the percent. |
| texture | "flat" | "dither" | "gradient" | "grain" | - | dither draws the filled part as dots, gradient fades it along the arc to full at its end, and grain speckles it, with a glow on dark pages. Hover fills gradient and grain in. Left out, the ring follows the Texture around it. |
| className | string | - | Extra classes. Later classes win. |
Accessibility
- Renders role="img", named by aria-label and the percent, like 'Tasks: 75%'. With no aria-label, the name is the percent.
- The number inside is hidden from screen readers, since the name already says it. With showValue={false} the name still does.
- The filled part uses the tone's color on the track. The track is quiet, so the percent carries the meaning, in text or in the name.
- The fill sweeps to a new value over --motion-duration-slow. Reduced motion jumps straight to it.
Examples
A percent, named by its label
<RingProgress value={75} aria-label="Tasks" /><RingProgress texture="dither" value={75} aria-label="Tasks" /><RingProgress texture="gradient" value={75} aria-label="Tasks" /><RingProgress texture="grain" value={75} aria-label="Tasks" />A count toward a target
<RingProgress value={3} max={4} aria-label="Sessions this week" /><RingProgress texture="dither" value={3} max={4} aria-label="Sessions this week" /><RingProgress texture="gradient" value={3} max={4} aria-label="Sessions this week" /><RingProgress texture="grain" value={3} max={4} aria-label="Sessions this week" />Beside its own words
<span className="flex items-center gap-2">
<RingProgress value={3} max={4} size="sm" showValue={false} aria-label="Forms returned" />
3 of 4 forms returned
</span><span className="flex items-center gap-2">
<RingProgress texture="dither" value={3} max={4} size="sm" showValue={false} aria-label="Forms returned" />
3 of 4 forms returned
</span><span className="flex items-center gap-2">
<RingProgress texture="gradient" value={3} max={4} size="sm" showValue={false} aria-label="Forms returned" />
3 of 4 forms returned
</span><span className="flex items-center gap-2">
<RingProgress texture="grain" value={3} max={4} size="sm" showValue={false} aria-label="Forms returned" />
3 of 4 forms returned
</span>One color, even when full
<RingProgress value={100} tone="brand" aria-label="Ticket sales" /><RingProgress texture="dither" value={100} tone="brand" aria-label="Ticket sales" /><RingProgress texture="gradient" value={100} tone="brand" aria-label="Ticket sales" /><RingProgress texture="grain" value={100} tone="brand" aria-label="Ticket sales" />A gradient ring that fills in on hover
<RingProgress value={68} tone="brand" texture="gradient" aria-label="CPU load" />