Progress Ring Stats
A free logo and stats section for React: circular progress rings that sweep to their value on scroll.
- Automated axe check
- Reduced-motion tested
- SSR build verified
- Motion
- 1 runtime package
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 the final value and label.
Measured in production
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: "Uptime", value: 99, suffix: "%" },
{ label: "Customer satisfaction", value: 96, suffix: "%" },
{ label: "Faster builds", value: 72, suffix: "%" },
{ label: "Test coverage", value: 88, suffix: "%" },
];
export function Example() {
return <StatProgressRing stats={stats} />;
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| stats | { label: string; value: number; suffix?: string }[] | required | 2 to 6 stats; each value is a percent from 0 to 100, rendered final in the SSR HTML. |
| 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
Progress Ring Stats deliberately ships without component-level CSS variables. The ring track and progress stroke read the border and foreground tokens, so they follow your theme automatically. Shape each stat through its own entry 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"
/>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 a single visually hidden string with its value and label to assistive technology, while the SVG and the visible number and label 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.