Animated circular progress ring for avatars, profile pictures or any circular content.
npx shadcn@latest add https://refinery.abhii.me/r/avatar-circle.json| Name | Type | Description |
|---|---|---|
| children | ReactNode | Content to be wrapped by the avatar circle (e.g., an image or initials div). Anything circular works. |
| size | "xs" | "sm" | "md" | "lg" | "xl" | Drives the wrapper dimensions and SVG ring geometry. Override with `className` + explicit w-/h- if you need a custom size. |
| strokeColor | string | Accepts any CSS color string to set the color of the animated ring. |
| strokeWidth | number | Width of the animated ring in pixels. |
| duration | number | Duration of a full draw/erase cycle in seconds. |
| ease | Easing | Easing[] | Easing function(s) for the animation. Can be a single easing or an array for keyframe easing. |
| showTrack | boolean | Whether to show a faint ghost track behind the ring (the full circle at low opacity). |
| lineCap | "butt" | "round" | "square" | Stroke-linecap for the animated ring. 'round' (default) gives a tapered tip, 'butt' gives a hard edge, and 'square' extends the line slightly beyond the endpoint. |
| className | string | Additional CSS classes to apply to the wrapper for custom styling. |
Below are usage code examples of different variant usecases shown above in the preview section.
A simple variant wrapping text initials. No image needed at all - works just as well with initials or an icon.
import { AvatarCircle } from "@/components/avatar-circle";
export default function AvatarCircleExample1() {
return (
<AvatarCircle
size="sm"
strokeColor="currentColor"
className="text-neutral-600 dark:text-neutral-400"
>
<div className="flex size-full items-center justify-center rounded-full bg-neutral-200 text-sm font-medium text-neutral-700 dark:bg-neutral-700 dark:text-neutral-200">
AP
</div>
</AvatarCircle>
);
}
Convenience AvatarCircleImage component that handles the img element and fallback logic for you.
import { AvatarCircleImage } from "@/components/avatar-circle";
export default function AvatarCircleExample2() {
return (
<AvatarCircleImage
src="https://res.cloudinary.com/dbglon4f7/image/upload/v1772550894/sample.jpg"
alt="Jordan"
size="lg"
showTrack={false}
duration={0.5}
/>
);
}
Dark background, white ring, no ghost track, smaller size & use of a fallback initials string when image fails to load.
import { AvatarCircleImage } from "@/components/avatar-circle";
export default function AvatarCircleExample3() {
return (
<AvatarCircleImage
src="https://res.cloudinary.com/dbglon4f7/image/upload/v1772550894/sample.jpg"
alt="Alex Chen"
fallback="AC"
size="md"
/>
);
}
Slow, dramatic draw with custom easing [0.16, 1, 0.3, 1] and 'butt' line cap for a hard edge, instead of the default tapered tip
import { AvatarCircleImage } from "@/components/avatar-circle";
export default function AvatarCircleExample4() {
return (
<AvatarCircleImage
src="https://res.cloudinary.com/dbglon4f7/image/upload/v1772550894/sample.jpg"
alt="Profile"
size="xl"
strokeColor="currentColor"
strokeWidth={1}
duration={1.2}
ease={[0.16, 1, 0.3, 1]}
lineCap="butt"
/>
);
}
I used an SVG circle for the ring and animate strokeDashoffset to create the looping draw-and-erase effect. The component calculates the radius and circumference from the chosen size and stroke width, so the ring stays aligned around the content. You can turn on showTrack for a faint background circle, adjust duration and easing, or change the stroke color, width, and line cap to match your avatar treatment.