Progress Circle

A determinate ring for quota, training and run progress — six tones, a ring or gauge track, an optional centre readout, and a sweep that animates from 12 o’clock. The default tone is deliberately info: a ring that only reports a figure is informational, and a wall of them in a table should not read as a wall of switched-on states. Reach for selected when the ring is tracking progress the user is actually driving.

tsx
import { ProgressCircle } from "@pestoui/core"

Tones

Only the indicator carries the tone; the track stays a hairline so a row of rings reads as one control. selected is the brand ring — the lime primary is a fill colour and stops being legible at four pixels of stroke, so the visible green stands in for it. neutral is for a ring that is chrome rather than signal.

Sizes & readout

Named sizes pin their own stroke and font size. Pass a number for anything in between — the stroke scales with the box.

Ring or gauge track

track="ring" is the default: one continuous hairline sitting behind the indicator. track="gauge" draws the remainder as its own arc instead, held off the indicator by a blank at each junction — it reads crisper at larger diameters, where a continuous track starts to look like a second value. gapPercent sizes that blank as a percent of the circumference, floored at one and a half strokes so the two round caps can never touch.

Range & formatting

min and max map an arbitrary reading onto the sweep — 38 in a 20–80 range fills 30% of the arc, and 30 is what the default readout shows. aria-valuenow still reports 38 against its own bounds, so the announced value is the real one. formatValue takes over the readout and receives both numbers, so a ring can show hours, a rating or a currency while the arc keeps tracking the percentage.

Centre slot

children replace the readout entirely — a stacked total, a glyph, a caption. The whole slot is aria-hidden, because the root already announces the reading through aria-valuetext and two live regions racing over one quantity is worse than one terse announcement. children win over showValue, so pass one or the other.

Counting the readout up

animateValue swaps the static readout for AnimatedNumber, so the number travels while the arc sweeps instead of snapping to the answer the arc is still on its way to. It counts the raw value and derives the percentage per frame, which is what lets one formatValue serve both the resting and the in-flight readout. The count eases out rather than springing — every spring here except the overdamped pair crosses its target, and a counter that crosses briefly displays a number that was never true.

Live value

The dash offset transitions, so the sweep animates on change.

Props

PropTypeDefaultDescription
valuenumber0Current reading, clamped into [min, max]. A non-finite value falls back to min.
minnumber0Lower bound of the range. Reported as aria-valuemin.
maxnumber100Upper bound. An inverted or zero-width range renders as empty rather than as NaN, which would paint no arc and announce nothing.
tone"info" | "success" | "warning" | "destructive" | "selected" | "neutral""info"Colour of the indicator stroke. The track stays neutral in every tone. info is the default because the ring reports a figure; selected is for progress the user is driving.
size"sm" | "default" | "lg" | number"default"Named size (36 / 48 / 64px) or an explicit pixel box. A number derives its stroke and readout from the box at the same ratios.
strokeWidthnumber—Overrides the stroke the size would otherwise pick.
track"ring" | "gauge""ring"ring is a continuous hairline behind the indicator. gauge draws the remainder as its own arc, separated by a blank at each junction.
gapPercentnumber5Blank per junction, as a percent of the circumference. Gauge track only, capped at 25% and floored at one and a half strokes so facing round caps never meet.
showValuebooleanfalseRenders the reading dead centre. Ignored when children are passed.
animateValuebooleanfalseCounts the readout up to its value instead of snapping to it, on the same clock the arc sweeps on.
formatValue(value: number, percent: number) => string—Formats the readout, and also supplies aria-valuetext. Receives the clamped value and its percentage, mid-count as well as at rest.
childrenReact.ReactNode—Centre slot — a caption, a glyph, a stacked total. Wins over showValue and is hidden from assistive tech, since the root already announces the reading.

Pesto UI · 117 documented components