Progress ring
Also called circular progress bar, radial progress, progress circle, or gauge
Circular progress drawn with an SVG stroke that fills around the ring, with the value in the middle. With a gap at the bottom, it becomes a gauge.
import { useEffect } from 'react';
import {
animate,
motion,
useMotionValue,
useReducedMotion,
useTransform
} from 'motion/react';
const SIZE = 120;
const STROKE = 10;
const RADIUS = (SIZE - STROKE) / 2;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
type Props = { value: number; label: string };
export function ProgressRing({ value, label }: Props) {
const reduceMotion = useReducedMotion();
const progress = useMotionValue(0);
// An offset of the full circumference hides the dash; 0 shows the whole ring.
const offset = useTransform(progress, (v) => CIRCUMFERENCE * (1 - v / 100));
const percent = useTransform(progress, (v) => `${Math.round(v)}%`);
useEffect(() => {
const controls = animate(progress, value, {
duration: reduceMotion ? 0 : 0.6,
ease: 'easeOut'
});
return () => controls.stop();
}, [progress, value, reduceMotion]);
return (
<div
role='progressbar'
aria-label={label}
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={100}
className='relative grid size-30 place-items-center'
>
{/* Rotated so the arc starts at 12 o’clock instead of 3. */}
<svg
viewBox={`0 0 ${SIZE} ${SIZE}`}
aria-hidden='true'
className='absolute inset-0 -rotate-90 fill-none'
>
<circle
cx={SIZE / 2}
cy={SIZE / 2}
r={RADIUS}
strokeWidth={STROKE}
className='stroke-muted'
/>
<motion.circle
cx={SIZE / 2}
cy={SIZE / 2}
r={RADIUS}
strokeWidth={STROKE}
strokeLinecap='round'
strokeDasharray={CIRCUMFERENCE}
style={{ strokeDashoffset: offset }}
className='stroke-blue-500'
/>
</svg>
<motion.span className='text-2xl font-semibold tabular-nums'>
{percent}
</motion.span>
</div>
);
}Add a 120px circular progress ring to the storage card: an SVG circle with a 10px stroke, a light gray track behind a blue arc that starts at 12 o’clock, and round line caps. Draw the arc with stroke-dasharray set to the circumference and animate stroke-dashoffset over 600ms ease-out when the value changes, counting the percentage in the center up at the same time. Add role="progressbar" with aria-valuenow, and set the value instantly under prefers-reduced-motion.
Paste into Layerwise or any AI coding tool.
What is Progress ring?
A progress ring is an SVG <circle> with a thick stroke and no fill. Setting stroke-dasharray to the circle’s circumference turns the outline into one long dash, and stroke-dashoffset slides that dash out of view: an offset equal to the circumference hides the whole ring, and an offset of 0 shows all of it. Rotate the circle -90° so it starts at 12 o’clock, and put the number in the middle as real text.
Rings fit where a bar would be too wide or too plain: storage used, a daily goal, an upload on a file tile, a score on a dashboard. Leave a gap at the bottom and the same technique becomes a gauge, like a speedometer or a performance score, often colored green, amber, or red by range.
Animate the offset with a short ease-out when the value changes, about 500 to 800ms, and count the number up alongside it. Give the ring role="progressbar" with aria-valuenow, or let the visible text carry the value and hide the SVG from screen readers. Under reduced motion, jump straight to the new value.
Often confused with
- Progress bar
- A progress bar fills a horizontal track and suits long tasks in a list. A progress ring shows the same kind of value in a compact circle, usually with the number inside.
- Spinner
- A spinner loops without a value because the wait has no known length. A progress ring always shows how much is done.
Related terms
All termsProgress bar
A horizontal bar that fills from left to right to show how much of a task is done, or slides in a loop while the total is still unknown.
Spinner
A small looping animation, usually a rotating ring, that shows the app is busy with a short task of unknown length.
Number ticker
A number that animates to its new value, either counting through the values in between or rolling each digit into place like an odometer.
SVG line drawing
An animation that makes an SVG outline look like it is being drawn by hand, by revealing its stroke from start to end.
Build it with Layerwise
Describe your app, watch it run in a live preview, and publish it with a database, storage, and sign-in built in.