Skip to content

Animated Circular Progress

A ring that fills to its value after it appears, with the value in the middle. It can also spin when the amount of work is not known.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

72%
import { AnimatedCircularProgress } from "@rdloom/react";

export default function AnimatedCircularProgressBasicExample() {
  return (
    <div className="flex w-full justify-center">
      <AnimatedCircularProgress value={72} label="Storage used" />
    </div>
  );
}

Installation

npx rdloom add animated-circular-progress

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/animated-circular-progress.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { AnimatedCircularProgress } from "@rdloom/react";

<div className="flex w-full justify-center">
  <AnimatedCircularProgress value={72} label="Storage used" />
</div>

Indeterminate

import { AnimatedCircularProgress } from "@rdloom/react";

export default function AnimatedCircularProgressIndeterminateExample() {
  return (
    <div className="flex w-full justify-center">
      <AnimatedCircularProgress isIndeterminate size={56} thickness={6} label="Importing contacts" />
    </div>
  );
}

Custom center

18of 20
import { AnimatedCircularProgress } from "@rdloom/react";

export default function AnimatedCircularProgressCustomCenterExample() {
  return (
    <div className="flex w-full justify-center">
      <AnimatedCircularProgress value={18} maxValue={20} size={112} thickness={10} label="Tasks done">
        <span className="relative text-center text-sm leading-tight">
          <span className="block text-lg font-semibold">18</span>
          of 20
        </span>
      </AnimatedCircularProgress>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
value

Current value between minValue and maxValue. Ignored while indeterminate.

number0
minValue

Value at which the ring is empty.

number0
maxValue

Value at which the ring is full.

number100
label

Accessible name of the progress bar, for example "Upload progress". Required unless aria-labelledby is given.

stringnone
size

Width and height of the ring in pixels.

number96
thickness

Width of the ring stroke in pixels.

number8
showValue

Shows the value as text in the middle of the ring.

booleantrue
isIndeterminate

Spins a short arc for work of unknown length. No value is announced.

booleanfalse
duration

Seconds the fill takes, or for one turn while indeterminate. Defaults to 0.9 for the fill and 1.2 for the spin.

numbernone
isPaused

Stops the spinning. The fill still moves to new values, only once.

booleanfalse
children

Replaces the value text in the middle of the ring.

nodenone

Accessibility

Role progressbar, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • Announced as a progress bar with its name and its percentage
  • The animation is not announced: only the final value is exposed

What your code must do

  • Exposes role=progressbar with aria-valuenow, aria-valuemin and aria-valuemax; while indeterminate aria-valuenow is omitted
  • Needs an accessible name from the label prop or aria-labelledby
  • The value text is real text in the server HTML, so the final value is readable with no JavaScript and no animation
  • The fill animates after mount only; visitors who ask for less motion (prefers-reduced-motion), and anything inside rdm-still, see the final ring at once
  • The spin can be stopped with isPaused (WCAG 2.2.2) and stops for reduced motion
  • In forced-colors (high contrast) mode the ring uses the system highlight and text colours
  • Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)

Guidelines

Use it when

  • A single measurable task such as an upload, a score or a quota
  • A compact progress display where a bar would not fit

Avoid it when

  • Several steps with names, where a stepper tells more
  • Work that finishes in under a second

Don't

  • Leaving the label off, which gives screen readers an unnamed progress bar
  • Showing a spinning ring for work that has a known amount

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-action-primary
  • --rd-color-border-default
  • --rd-color-text-default