Progress Ring Stats
A free logo and stats section for React: circular progress rings that sweep to their value on scroll.
Rollout results
What changed after teams automated the release path
Median across 412 engineering teams, measured in production.
Customise
Code
const stats = [
{
label: "Deploy time cut",
value: 44,
suffix: "%",
detail: "22 min median, down from 39",
},
{
label: "Services on auto-rollback",
value: 89,
suffix: "%",
detail: "190 of 214",
},
];
<StatProgressRing stats={stats} />Progress Ring Stats is a free animated stats section for Next.js and React, styled with Tailwind. It renders circular progress stats as SVG rings: the first time the section scrolls into view, every ring sweeps from empty to its percentage with Motion while the number in its centre counts up to match, rings starting 100 milliseconds apart.
The final number and the fully drawn ring of every stat are rendered in the server HTML, so crawlers, no-JS visitors and reduced-motion users always see the real values (enhancement-only animation). The sweep writes the stroke offset and the count writes into the DOM through a single Motion value subscription with zero React re-renders per frame, tabular numerals and a reserved width hold layout perfectly stable, and screen readers only ever hear each ring's finished figure: its label, its final value and its detail line where the stat has one.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/stat-progress-ringUsage
import { StatProgressRing } from "@/components/stat-progress-ring";
const stats = [
{
label: "Deploy time cut",
value: 44,
suffix: "%",
detail: "22 min median, down from 39",
},
{
label: "Rollbacks avoided",
value: 61,
suffix: "%",
detail: "One release in nine went back. Now one in 23.",
},
{
label: "Alerts closed first-touch",
value: 78,
suffix: "%",
detail: "No escalation, no second responder",
},
{
label: "Services on auto-rollback",
value: 89,
suffix: "%",
detail: "190 of 214",
},
];
export function Example() {
return <StatProgressRing stats={stats} />;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| stats | { label: string; value: number; suffix?: string; detail?: string }[] | required | 2 to 6 stats; each value is a percent from 0 to 100, rendered final in the SSR HTML. detail is the optional claim line under the label, spoken after the value. |
| size | number | 160 | Ring diameter in pixels; stroke width and radius derive from it. |
| duration | number | 1.2 | Sweep and count-up duration in seconds per ring (--dur-4). |
| className | string | undefined | Additional classes merged onto the section. |
Customisation
Three CSS variables colour the rings, and all three default to your theme tokens: --stat-ring-track for the unfilled track (--border), and --stat-ring-sweep-from and --stat-ring-sweep-to for the two stops of the gradient the swept arc is painted with (--primary and the accent token). Set them at :root or on any ancestor to run the sweep in a different pair. Shape each stat through its own entry, adding detail for the claim line under the label, and tune the section with the props: size sets the ring diameter, duration paces the sweep against your motion system, and className restyles the grid, for example to change the gap or column widths:
<StatProgressRing
stats={stats}
size={200}
duration={2}
className="max-w-4xl gap-y-14"
/>
/* in your stylesheet */
:root {
--stat-ring-sweep-from: var(--primary);
--stat-ring-sweep-to: var(--ring);
}Frequently asked questions
- Is Progress Ring Stats free for commercial use?
- Yes. Progress Ring Stats is MIT licensed, so you can use it in personal and commercial projects without attribution.
- Do the rings hurt SEO or accessibility?
- No. The server HTML contains every final number and a fully drawn ring, so crawlers and no-JS visitors read the real values without JavaScript. Each ring exposes one visually hidden string to assistive technology, carrying its label, its final value and, when the stat supplies one, its detail line, while the SVG and the visible number, label and detail are hidden from it, so screen readers never hear an intermediate frame. With reduced motion enabled nothing sweeps or counts and the final state renders immediately.
- How do I change the ring size or the number suffix?
- Set the size prop for the ring diameter; the stroke width and radius scale with it. Add a suffix such as a percent sign on each stat to render it after the counting number. Values are percentages from 0 to 100 and the count keeps the same rounding as the final number on every frame.