Circular Progress
An animated percentage ring progress / gauge, supporting gradients, gauge arcs, and a counting reveal.
Shows progress as an SVG ring, with the arc and the central number advancing on a single synchronized animation beat, supporting either a full ring or a gauge.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add circular-progress.enCircular Progress is a React circular progress bar (a progress ring with a percentage) built with SVG and Motion. The arc and the center number are driven by one animated value, with gradient colors, rounded caps and an optional glow; set arc below 360 to turn it into a half-circle or 270-degree gauge.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<CircularProgress/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- Upload, export or batch-job progress where you have an exact percentage
- A "profile completion" or onboarding checklist indicator
- Plan usage, storage or quota summary cards
- KPI attainment on a dashboard
- Not a good fit: gauges that need ticks, a needle and colored zones, use Gauge Meter
Installation
npx webberui@latest add https://webberui.com/r/circular-progress.en.jsonOr, once registries are configured in components.json, install it as @webberui/circular-progress.
Usage
import { CircularProgress } from "@/components/ui/circular-progress";
<CircularProgress value={72} />;To make a gauge: set arc to less than 360, then use startAngle to align the direction of the opening.
<CircularProgress value={86} arc={270} startAngle={135} />;Examples
Update with upload progress
When value changes, the arc and number tween from the current value to the new one instead of restarting from 0, so you can feed progress events straight in.
"use client";
import * as React from "react";
import { CircularProgress } from "@/components/ui/circular-progress";
export function UploadProgress({ file }: { file: File }) {
const [percent, setPercent] = React.useState(0);
React.useEffect(() => {
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) setPercent((e.loaded / e.total) * 100);
};
xhr.open("POST", "/api/upload");
xhr.send(file);
return () => xhr.abort();
}, [file]);
return <CircularProgress value={percent} size={120} duration={0.4} decimals={0} />;
}Custom center content
children replaces the default percentage, which suits non-percentage labels like "3 / 5 steps"; value still needs to be converted to 0–100.
<CircularProgress value={(3 / 5) * 100} size={140} strokeWidth={10}>
<span className="text-2xl font-semibold">3 / 5</span>
<span className="text-xs text-neutral-500">Setup steps</span>
</CircularProgress>Solid color with glow for storage usage
A string color gives a single color, and glow adds a drop-shadow in that same color; trackColor sets the track.
<CircularProgress
value={82.4}
decimals={1}
color="#f97316"
trackColor="#fed7aa"
glow
rounded={false}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | — | Progress percentage (0 to 100) |
size | number | 160 | Component side length (px) |
strokeWidth | number | 12 | Ring stroke width (px) |
color | string | string[] | cyan-indigo-magenta gradient | A single color string, or an array for a gradient along the arc |
trackColor | string | low-opacity currentColor | Track color |
arc | number | 360 | Total span of the arc (degrees); anything below 360 makes it a gauge |
startAngle | number | -90 | Start angle (degrees); -90 is straight up |
duration | number | 1.2 | Duration of the reveal animation (seconds) |
delay | number | 0 | Delay before the reveal starts (seconds) |
rounded | boolean | true | Whether the end caps are rounded |
showValue | boolean | true | Whether to show the central percentage number |
decimals | number | 0 | Decimal places of the number |
suffix | string | "%" | Suffix after the number |
glow | boolean | false | Adds a soft glow in the same color to the progress arc |
children | ReactNode | — | Custom central content, replacing the default number |
Accessibility
- When the user has "reduce motion" enabled at the system level, it renders at the final value directly, without playing the counting or sweeping animation
- The
svgcarriesrole="img"and anaria-label, announcing the current percentage
FAQ
Does it work with the Next.js App Router?
Yes. The source already has "use client", so import it from a Server Component page. The server and first render output 0, and the animation to the target value runs after mount.
What happens with values above 100 or below 0?
value is clamped to 0–100 and NaN is treated as 0, so the arc and number never overflow.
Can I change the gradient direction?
An array color creates a top-left to bottom-right linearGradient, with stops spread evenly in array order. The direction is not exposed as a prop; edit x1, y1, x2 and y2 in the source if you need another one.
How do screen readers announce it?
The svg has role="img" and an aria-label with the target value plus suffix, for example "72%". With reduced motion enabled, the final value renders immediately with no animation.
What are the dependencies?
motion, clsx, tailwind-merge, and the cn helper in @/lib/utils. Without the CLI, copy the source from the Manual tab.