UI.MD

Progress

A progress bar, and the same progress as a ring, for showing completion toward a task.

Installation

$
npx shadcn@latest add https://ui.mattdowney.com/r/progress.json

Usage

import { Progress, ProgressRing } from "@/components/ui/progress"

Examples

Default

The indicator eases to its value (300ms ease-out). Remount via key to replay the fill from zero.

x

Ring

The same progress as a ring, for the places a full-width bar does not fit: a card corner, a row, a button's neighbor. It eases to its value the same way the bar does.

x

Ring sizes

16, 24, and 48px. The stroke is an eighth of the diameter at every one, so all three read as the same ring rather than three different weights, and that holds if you size it past these three with a className.

x
sm
x
default
x
lg

Ring weights

The eighth-of-diameter stroke is tuned to the shipped 16-48px range; perceived weight grows with area, so a ring sized well past that reads the same ratio as a donut. weight="thin" halves the stroke to a sixteenth for exactly that case, so a bigger ring stays this component instead of a hand-rolled fork.

72%x
default
72%x
thin

Ring with a value

Children sit in the middle of the ring, out of the flow, so the box stays the size it was asked for. lg is sized for exactly this: a smaller ring leaves the label touching the arc.

72%x

API Reference

PropTypeDefaultDescription
valuenumber | null-Current progress between min and max. On the bar, omitting it (or passing null) is the indeterminate state: a pulsing one-third fill instead of an empty track. The ring has no such state and draws an empty track instead, because a ring is a quantity and a spinning one is a waiting state, which is a different thing. The bar's fill respects min/max and flips edges automatically in RTL.
sizeProgressRing"sm" | "default" | "lg""default"Ring diameter: 16, 24, or 48px. The stroke is a fixed ratio of it, so the three read as one ring. lg is sized to hold a number in the middle.
weightProgressRing"default" | "thin""default"Stroke as a ratio of the diameter: an eighth, or a sixteenth for thin. Use thin when sizing the ring past the shipped three with a className, where the default ratio reads as a donut.
childrenProgressRingReactNode-Content centered inside the ring, e.g. the percentage. Absolutely placed, so it never changes the ring's own size.
minnumber0Lower bound of the range.
maxnumber100The value at which the bar reads full. Drives both the visual fill and the value reported to assistive tech.
getValueLabel(value, max) => string-Overrides the accessible label announced for the current value.