Skip to content

ComponentsFeedback

Progress Circle

Circular progress on React Aria's ProgressBar. Determinate or indeterminate, in four sizes and seven colors, with the value, your own text or an icon in the middle of the ring. Respects reduced motion.

React AriaSource
18%

Installation

pnpm dlx shadcn@latest add @desyne/progress-circle

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { ProgressCircle } from "@/components/ui/progress-circle";
tsx
<ProgressCircle aria-label="Uploading photos" value={64} />

<ProgressCircle aria-label="Syncing" isIndeterminate />

Also exported from progress-bar

ProgressCircle used to live in progress-bar.tsx, which still re-exports it, so existing @/components/ui/progress-bar imports keep working.

When to use

  • Progress Circle: progress in a compact or square space, such as cards, dashboards, avatars and buttons, or a goal shown as a ring.
  • Progress Bar: progress with a visible label in forms, lists and dialogs.
  • Meter: a level within a range that isn't a task (storage, quota).
  • Spinner: plain waiting with no progress to report.

Anatomy

tsx
<ProgressCircle>            {/* role="progressbar" */}
  {/* svg: track circle + fill circle (aria-hidden) */}
  {/* center: value text or children */}
</ProgressCircle>
PartRendersNotes
ProgressCircle<div role="progressbar">React Aria ProgressBar. Sets aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext.
Ring<svg aria-hidden>Track at 15% tone, fill drawn with stroke-dashoffset (500ms transition). Spins when indeterminate.
Center<span>The formatted value (md and up), or children when passed.

Examples

Sizes

sm (20px) for inline status next to text, md (48px, default), lg (80px) and xl (120px) for dashboards. The value is shown from md up. strokeWidth overrides the ring thickness.

64%
64%
64%

Indeterminate

isIndeterminate spins a quarter ring and hides the value. With reduced motion, it pulses instead of spinning.

Saving changes…

Colors

color takes any tone; the track uses the same tone at 15%.

40%
50%
60%
70%
80%
90%

Custom center content

children replaces the value in the middle of the ring. Pass a function to read the render props (percentage, valueText, isIndeterminate). Set valueLabel so screen readers hear the same thing you show.

6/8workouts
74%of 10 GB

Recipes

Goals

Small circles next to text, each labelled by its heading with aria-labelledby, and maxValue set to the goal so the ring maps to real units.

  • 82%

    Activation

    412 of 500 new teams

  • 87%

    Retention

    Week-4 retention 61% · target 70%

  • 100%

    NPS responses

    1,240 of 1,000 collected

Accessibility

  • Renders role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext. Indeterminate circles omit the value.
  • There's no label prop, so always pass aria-label or aria-labelledby.
  • aria-valuetext is your valueLabel, or the value formatted with formatOptions. Custom center content is visual only, so keep valueLabel in sync with it.
  • The SVG is aria-hidden.
  • Respects prefers-reduced-motion: the fill jumps to each value, and the indeterminate ring pulses instead of spinning.
  • Progress circles aren't live regions. To announce completion, update a role="status" element.

Styling

Data slots

SlotElement
data-slot="progress-circle"Root
data-slot="progress-circle-label"Center content

Tone variables

color sets --tone on the root. The track uses stroke-(--tone)/15 and the fill stroke-(--tone).

API Reference

Prop

Type

Also accepts every prop of React Aria's ProgressBar.

  • Progress Bar: the same information as a bar, with a label.
  • Spinner: waiting without progress.
  • Meter: a level within a range.