Progress ring
FreeDeterminate progress in a ring that eases to each value, draws a check when done and a cross when it fails.
pnpm dlx shadcn@latest add https://caliperui.site/r/progress-ring.jsonWhen to use #
Use it for
- Uploads, exports and syncs with a known size.
- Compact progress in a row or on an avatar.
Skip it when
- Work with no known end; use a spinner.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/progress-ring.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { useState } from "react" import { Button } from "@/components/ui/button" import { ProgressRing } from "@/components/ui/progress-ring" export function ProgressRingDemo() { const [value, setValue] = useState(35); const [failed, setFailed] = useState(false); return ( <div className="grid justify-items-center gap-4"> <div className="flex items-center gap-6"> <ProgressRing value={value} label="Uploading board-pack.pdf" size={56} showValue status={failed ? "error" : value >= 100 ? "done" : "active"} /> <ProgressRing value={value} label="Uploading board-pack.pdf, small" size={24} thickness={3} status={failed ? "error" : value >= 100 ? "done" : "active"} /> </div> <div className="flex flex-wrap justify-center gap-2"> <Button size="sm" variant="secondary" onClick={() => { setFailed(false); setValue((v) => Math.min(100, v + 20)); }}>Add 20%</Button> <Button size="sm" variant="secondary" onClick={() => setFailed(true)}>Fail</Button> <Button size="sm" variant="ghost" onClick={() => { setFailed(false); setValue(0); }}>Reset</Button> </div> </div> ); }
API reference #
ProgressRing
component
Also accepts the native props of its root element and a ref.
Accessibility #
It exposes progressbar semantics, with aria-label, aria-valuemin, aria-valuemax, aria-valuenow, aria-valuetext and aria-hidden kept in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.