// preview in
saved for every page

ProgressCircle

A ring that shows how far a task has gone, sized for cards, tiles and toolbars. When there is room for a full-width bar, use Progress.

Built on SVG ringPattern WAI-ARIA progressbarServer componentSource ↗
minimal · light · #3fa34d
72%
Exporting report
tone
showValue

Installation

Set up Camaleo once (Installation), then import what you need. It renders on the server.

import { ProgressCircle } from "@camaleo/react";

Anatomy

A root with the progressbar role holds the ring (an SVG with a track and an arc, plus the value on top) and an optional label below. Every part has a data-slot you can target from CSS.

Examples

Every example is a real file you can copy. They all wear the skin, mode and brand from the bar above.

minimal · light · #3fa34d
65%
Syncing photos
64%
Uploading report.pdf
40%
Paused · no connection
30%
Failed · file too large

Stuck or failed

Use tone="warning" when the task is stuck and tone="danger" when it failed. Say why in the label too, so the color is not the only signal.

60%
Toolbar, 40 px
60%
Card, 56 px
60%
Summary, 80 px

Sizes

Change size and strokeWidth together. A thin stroke on a big ring looks faint, and a thick one on a small ring crowds the number.

Preparing backup

Unknown duration

Pass value={null} when you can't measure the work yet: the number hides and the arc spins (it pulses with reduced motion). Leaving value out is not the same, it means 0%.

3/5
Lessons done

Count instead of percent

Set max to the real total and show "3/5" in valueLabel. getAriaValueText gives screen readers the words, "3 of 5 lessons", instead of "60%".

Backup4 of 6 folders

Tiny ring next to text

Below about 40 px the number doesn't fit. Hide it with showValue={false}, write the value beside the ring and name the ring with aria-label.

78 GB
Storage used

A measurement, not a task

Storage used never moves toward "done". Pass role="meter" so screen readers don't announce it as a task in progress.

2/6
Uploading photos

Announce when it's done

Click until it finishes. Screen readers don't announce every change of the ring, so a role="status" message says when all the photos are in.

In context

Rings fit where a bar would be too wide: next to a title, in a card or in a row of goals. These are whole pieces of UI you can copy, in the skin you picked.

Uploading to Shared drive

4 of 6 files · about 1 minute left

4/6
Upload in progressCard · ProgressCircle · Button

Today's activity

Wednesday, October 28, 2026

84%
Steps
30/45
Active minutes
10/12
Stand hours
Daily goalsCard · ProgressCircle
7/12

TypeScript for React developers

Next lesson: Generic components

Course cardCard · ProgressCircle · Button

Guidelines

40%
Importing contacts
DoSay what is running.

"Importing contacts" tells people what they are waiting for, and becomes the ring's accessible name.

40%
Don’tA ring with just a number.

40% of what? Without a label, people can't tell which task it tracks, and screen readers only hear "Progress".

30%
Upload failed · try again
DoSave danger for a real problem.

A red ring next to "Upload failed" points people to the one thing that needs them.

25%
Profile setup
Don’tRed for a task that is just running.

A profile setup at 25% is normal. Red there teaches people to ignore red when something really breaks.

Props

PropTypeDefaultDescription
valuenumber | null0How far the task has gone, between min and max (clamped). null means the duration is unknown and the ring spins; leaving it out means 0.
minnumber0Minimum scale value.
maxnumber100Maximum scale value.
sizenumber56Outer diameter of the ring, in pixels.
strokeWidthnumber5Thickness of the track and the arc, in pixels.
toneProgressCircleTone"brand"Color of the arc: warning when the task is stuck, danger when it failed.
labelReactNode—Text below the ring, which also names it for screen readers unless aria-label is set. Without either, the name is "Progress" (or the MessagesProvider text).
showValuebooleantrueShows the value inside the ring. It is always hidden while value is null.
valueLabelReactNode—Visible text that replaces the percentage, like "3/5". A string is also what screen readers hear; otherwise pass getAriaValueText.
getAriaValueTextfunction`valueLabel` when it is a string; otherwise the percentageWhat screen readers hear as the value (aria-valuetext). Gets the shown percentage (like "40%") and the value, both null while indeterminate.

Native <div> attributes pass through, like aria-label, aria-valuetext and role.

Styling hooks

Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the ring itself.

Attributes

data-tonebrand · warning · danger, on the root and the indicator
data-indeterminatepresent when value is null

Tokens it reads

Color

--cml-accent-text--cml-danger--cml-fg--cml-fg-secondary--cml-surface-raised--cml-warning

Type & motion

--cml-duration-base--cml-ease-standard--cml-font-control--cml-font-label--cml-ring-linecap

Accessibility

—Not focusable. It reports status; the controls around it take the focus.
Was this page helpful? · Edit this page on GitHub