ProgressRing
SVG circular progress indicator with smooth Framer Motion animation on strokeDashoffset.
Use and inspectUse this pattern for the stated interaction and keep its keyboard path visible. Reset preview remounts the local example; it does not establish production persistence or a backend integration. Add a reduced-motion fallback where the source animates.Next: inspect the source and adapt its event or data boundary.
ProgressRingSVG circular progress indicator with smooth Framer Motion animation on strokeDashoffset.
"use client";
import { motion } from "framer-motion";
type ProgressRingProps = {
value: number;
size?: number;
strokeWidth?: number;
color?: string;
showLabel?: boolean;
};
export function ProgressRing({
value,
size = 80,
strokeWidth = 7,
color = "currentColor",
showLabel = true,
}: ProgressRingProps) {
const r = (size - strokeWidth) / 2;
const circ = 2 * Math.PI * r;
const offset = circ - (Math.min(Math.max(value, 0), 100) / 100) * circ;
return (
<div style={{ position: "relative", width: size, height: size, display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: "rotate(-90deg)" }}>
<circle cx={size/2} cy={size/2} r={r} fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeOpacity={0.1} />
<motion.circle
cx={size/2} cy={size/2} r={r}
fill="none" stroke={color}
strokeWidth={strokeWidth} strokeLinecap="round"
strokeDasharray={circ}
initial={{ strokeDashoffset: circ }}
animate={{ strokeDashoffset: offset }}
transition={{ duration: 1, ease: "easeOut" }}
/>
</svg>
{showLabel && (
<span style={{ position: "absolute", fontSize: "14px", fontWeight: 600 }}>{value}%</span>
)}
</div>
);
}Install
npm install framer-motion
This component requires framer-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value* | number | — | Progress 0–100 |
size | number | 80 | SVG size in pixels |
strokeWidth | number | 7 | Ring stroke width |
color | string | "currentColor" | Ring color |
showLabel | boolean | true | Show percentage label in center |