72%
React + MotionLoaders
Progress ring
A round progress indicator with the percentage in the middle that springs smoothly to each new value.
Settings
72%
112px
Code
import { motion } from "framer-motion";
const RADIUS = 42;
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
interface WatchProgressRingProps {
color?: string;
/** Progreso de 0 a 100. */
progress?: number;
size?: number;
}
export default function WatchProgressRing({
color = "#0071e3",
progress = 72,
size = 112,
}: WatchProgressRingProps) {
return (
<div
role="progressbar"
aria-valuenow={progress}
aria-valuemin={0}
aria-valuemax={100}
className="relative grid place-items-center text-[#1d1d1f] dark:text-[#f5f5f7]"
style={{ width: size, height: size }}
>
<svg viewBox="0 0 100 100" className="absolute inset-0 -rotate-90">
<circle
cx="50"
cy="50"
r={RADIUS}
fill="none"
strokeWidth="9"
className="stroke-black/5 dark:stroke-white/10"
/>
<motion.circle
cx="50"
cy="50"
r={RADIUS}
fill="none"
stroke={color}
strokeWidth="9"
strokeLinecap="round"
strokeDasharray={CIRCUMFERENCE}
animate={{ strokeDashoffset: CIRCUMFERENCE * (1 - progress / 100) }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>
</svg>
<motion.strong
key={progress}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="text-xl"
>
{progress}%
</motion.strong>
</div>
);
}
Prompt for AI
Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.
More in Loaders
Where to use it
A circular progress bar is ideal when the number itself matters: how complete a user profile is, how much of a course a student has finished or how close someone is to today’s step goal in a fitness app. The ring springs to each new value, much like the activity rings on a smartwatch.
Only use it when you know the real percentage, because a ring that jumps backwards loses people’s trust. One ring per card is enough. Pick a color with good contrast against the grey track, especially on light backgrounds.