Progress ring
Free · MITReact Native · ExpoA circular progress ring that fills on a spring, laps itself past 100 % with a gap that lifts the lap off the ring, and bounces once with a success haptic when it completes.
Installation
$ npx shadcn@latest add https://goodui.dev/native/r/progress-ring.jsonThen run npx expo install --fix so react-native-reanimated, react-native-svg match your Expo SDK. Files land in components/goodui-native/progress-ring.
Usage
import { ProgressRing } from "@/components/goodui-native/progress-ring/progress-ring";
import { Text } from "@/components/goodui-native/foundation/text";
export function StepsGoal({ steps }: { steps: number }) {
return (
<ProgressRing value={steps / 10000} size="lg" accessibilityLabel="Steps">
<Text size="title1" weight="medium" tabular>{steps.toLocaleString()}</Text>
<Text size="footnote" tone="secondary">of 10,000 steps</Text>
</ProgressRing>
);
}When to use
- Goals and quotas: steps, move minutes, savings targets, storage used, course completion.
- Several related goals at once: pass rings for activity-rings style concentric progress.
- Determinate tasks with a known fraction, like an upload, when the ring sits next to its label.
- Waiting without a known fraction: use the Spinner from the foundation, or a button that returns its promise.
- Placeholders while content loads: use skeleton.
- Trends over time: use sparkline.
Examples
Activity rings
<ProgressRing
size="lg"
rings={[
{ label: "Move", value: 1.35, tone: "danger" },
{ label: "Exercise", value: 0.72, tone: "success" },
{ label: "Stand", value: 0.5, tone: "accent" },
]}
/>Rolling percentage in the center
<ProgressRing value={uploaded / total} showValue tone="success" accessibilityLabel="Upload" />Small, inline
<ProgressRing value={0.4} size="sm" tone="warning" accessibilityLabel="Storage" />API reference
| Prop | Type | Default |
|---|---|---|
| value Progress of a single ring, 0 to 1. Above 1 the ring laps itself. Ignored when `rings` is set. | number | 0 |
| rings Concentric rings, outermost first, activity-rings style. | ProgressRingSpec[] | — |
| size Diameter: `sm` 44, `md` 96, `lg` 168, or a number in points. | "sm" | "md" | "lg" | number | "md" |
| thickness Stroke width. Defaults to about a twelfth of the diameter. | number | — |
| tone Color of a single ring, and the default for `rings`. | "success" | "warning" | "danger" | "accent" | "neutral" | "accent" |
| children Center content. Replaces `showValue`. | ReactNode | — |
| showValue Show the first ring's percentage in the center as a rolling number. | boolean | false |
| animateOnMount Draw the arcs in from zero when the ring mounts. Default `true`. | boolean | true |
| haptic Haptic when a ring reaches 100 % after mount. `false` turns it off. | HapticName | false | "success" |
| on The surface the ring sits on. The gap that separates a lap from the ring under it uses this color. | "surface" | "background" | "surface" |
| accessibilityLabel Label for a single ring, for example "Upload". Rings use their own labels. Same as `labels.progress`. | string | — |
| labels Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. | Partial<ProgressRingLabels> | — |
| style Style for the outer container. | StyleProp<ViewStyle> | — |
| testID Identifier for end-to-end tests. | string | — |
Motion and haptics
- Arcs fill on the smooth spring; on mount they draw in from zero, outer ring first, 90 ms apart.
- Past 100 % a lap arc draws on top, and a disc in the surface color under its head separates it from the ring below.
- Crossing 100 % after mount scales the ring to 1.06 and back on the bouncy spring, once.
- The center percentage rolls with rolling-number.
- Reduced motion: arcs jump to their value, there is no bounce, and the number changes without rolling.
- Haptic: success when a ring crosses 100 % after mount (configurable with the haptic prop); never on mount
- Arc length, lap and gap position are animated props on the UI thread; React renders only when values change.
Accessibility
- accessibilityRole is progressbar; a single ring reports accessibilityValue from 0 to 100.
- The label lists every ring with its percentage, for example 'Move 135 percent, Exercise 72 percent'.
- Reaching 100 % is announced as '<label> complete'.
- Center content is hidden from screen readers because the ring's label already carries the value.
Notes for AI
- value is a fraction, not a percent: pass 0.72, not 72.
- Set on="background" when the ring sits on the screen background instead of a card, so the lap gap matches.
- For activity rings pass rings (outermost first) with a label each; value and accessibilityLabel are then ignored.
- Put your own text in children for units like 'of 10,000 steps'; showValue only renders the percentage.
Related
A number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.
A small line or area chart that draws itself in and morphs to new data; press and drag to scrub, and a dot rides the curve while the line beyond your finger dims and every point ticks.
Loading placeholders whose shimmer is one light passing diagonally over the whole screen, plus presets and a Swap that eases from the skeleton's height to the content's while it cross-fades.