Progress Ring
PreviewAn animated circular progress indicator with a centered label.
- progress
- ring
- chart
- status
Preview
Installation
The CLI command below is illustrative until the registry endpoint ships. Until then, copy the source from the Code tab into components/fmui/. Every component expects Tailwind CSS v4, the cn helper (clsx + tailwind-merge), and Hugeicons.
$ pnpm dlx shadcn@latest add https://fmui.dev/r/progress-ring.jsonprogress-ring/progress-ring.tsx
Usage
example.tsx
import { ProgressRing } from "@/components/fmui/progress-ring";
<ProgressRing value={64} label="Storage" />Variants
Compact
Reduce size and strokeWidth for inline use.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | - | Progress from 0 to 100. |
| size | number | 96 | Diameter in pixels. |
| strokeWidth | number | 8 | Ring thickness. |
| label | string | - | Caption under the percentage. |
Accessibility
- Exposes role=progressbar with aria-valuenow, min, and max.
Source
"use client";
import { cn } from "@/lib/utils";
export interface ProgressRingProps {
/** 0 to 100. */
value: number;
size?: number;
strokeWidth?: number;
label?: string;
className?: string;
}
export function ProgressRing({ value, size = 96, strokeWidth = 8, label, className }: ProgressRingProps) {
const clamped = Math.min(100, Math.max(0, value));
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference * (1 - clamped / 100);
return (
<div
role="progressbar"
aria-valuenow={Math.round(clamped)}
aria-valuemin={0}
aria-valuemax={100}
aria-label={label ?? "Progress"}
className={cn("relative inline-flex items-center justify-center", className)}
style={{ width: size, height: size }}
>
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="-rotate-90" aria-hidden="true">
<circle cx={size / 2} cy={size / 2} r={radius} fill="none" strokeWidth={strokeWidth} className="stroke-border" />
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeDasharray={circumference}
strokeDashoffset={offset}
className="stroke-primary transition-[stroke-dashoffset] duration-500 ease-out-strong"
/>
</svg>
<span className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-lg font-semibold tracking-[-0.02em] text-foreground tabular-nums">
{Math.round(clamped)}%
</span>
{label && <span className="text-[11px] text-muted-foreground">{label}</span>}
</span>
</div>
);
}
Added Oct 23, 2026. Last updated Oct 23, 2026.