Progress Ring
loomui/progress-ringFreeComponent
A circular progress ring whose value springs to its target, so a number that changes mid travel keeps the speed it already had.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/progress-ring.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"6import { useSpring, type SpringOptions } from "@/registry/lib/use-spring"78export interface ProgressRingProps extends Omit<9 React.ComponentProps<"div">,10 "children"11> {12 /** Where the ring is now. */13 value: number14 /** What a full ring means. */15 max?: number16 /** Outside diameter in pixels. */17 size?: number18 /** Stroke width in pixels. */19 thickness?: number20 /** How the value travels when it changes. */21 spring?: SpringOptions22 /** Print the number in the middle. */23 showValue?: boolean24 /** Turns the value into the text in the middle. */25 format?: (value: number) => string26 /** Accessible name. Give it one if the ring is the only label. */27 label?: string28 /** Render the ring at its value without animating to it. */29 disabled?: boolean30 /** Children sit in the middle instead of the number. */31 center?: React.ReactNode32}3334/** The box the ring is drawn in. Every measurement is a share of this. */35const BOX = 1003637const clamp = (value: number, max: number) =>38 Math.min(Math.max(value, 0), max) / (max || 1)3940/**41 * A ring that fills to a value, on a spring.42 *43 * The arc is drawn by walking the stroke's own dash offset around the circle.44 * That is a paint rather than a composite, which is the one place this steps45 * outside transform and opacity, and there is no way to draw an arc without it.46 * It stays cheap because only the stroke is repainted, never the layout.47 *48 * The value springs rather than tweens, so a target that changes while the ring49 * is still moving keeps the speed it already had. A progress ring that restarts50 * from a standstill every time the number updates is the tell.51 */52export function ProgressRing({53 value,54 max = 100,55 size = 120,56 thickness = 10,57 spring,58 showValue = true,59 format,60 label,61 disabled = false,62 center,63 className,64 style,65 ...props66}: ProgressRingProps) {67 const arcRef = React.useRef<SVGCircleElement>(null)68 const textRef = React.useRef<HTMLSpanElement>(null)6970 const radius = (BOX - thickness) / 271 const circumference = 2 * Math.PI * radius72 const target = clamp(value, max)7374 const paint = React.useCallback(75 ({ fill = 0 }: Record<string, number>) => {76 const arc = arcRef.current77 if (arc) {78 arc.style.strokeDashoffset = `${circumference * (1 - fill)}`79 }8081 const text = textRef.current82 if (text) {83 const shown = fill * max84// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
