Skip to content
All Taste components

Loaders

Progress ring

Circular progress that draws its arc up to the value, for storage, goals or task completion.

3 tones · staggered draw · centre value

72%
Storage
45%
Tasks done
90%
Monthly goal
import { cn } from "@/lib/utils";

const tones = { primary: "stroke-primary", info: "stroke-info", success: "stroke-success" } as const;

interface ProgressRingProps {
  value: number;
  label: string;
  tone?: keyof typeof tones;
  /** Stagger several rings: 0, 1, 2… */
  index?: number;
}

export function ProgressRing({ value, label, tone = "primary", index = 0 }: ProgressRingProps) {
  return (
    <figure className="grid justify-items-center gap-2 text-[12.5px] text-fg-muted">
      <div role="progressbar" aria-valuenow={value} aria-valuemin={0} aria-valuemax={100} aria-label={label} className="relative grid size-[76px] place-items-center">
        <svg viewBox="0 0 40 40" aria-hidden className="absolute inset-0 size-full -rotate-90">
          <circle cx="20" cy="20" r="16" pathLength={1} fill="none" strokeWidth={3.5} className="stroke-surface-2" />
          <circle
            cx="20" cy="20" r="16" pathLength={1} fill="none" strokeWidth={3.5} strokeLinecap="round"
            strokeDasharray={1} strokeDashoffset={1 - value / 100}
            className={cn(tones[tone], "animate-draw motion-reduce:animate-none")}
            style={{ animationDelay: index * 120 + "ms" }}
          />
        </svg>
        <b className="text-base font-medium tabular-nums text-fg">{value}%</b>
      </div>
      <figcaption>{label}</figcaption>
    </figure>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.