Progress

stable
Since 1.2
Linear and circular progress with four label placements, full-ring and half-gauge shapes, and a distinct indeterminate state.
Feedback
primitive
Display
Documented
import { Progress } from '@ivim/native/components';

When to use

Use when
A task has a measurable completion, such as an upload or an import.
A quota or capacity should be shown against its limit — use ProgressCircle.
A known task is running with unknown duration — use isIndeterminate.
Don't use when
Content is simply not ready yet and no task is running — use Spinner, which carries no task semantics.
The value is something the user sets — use Slider.
The steps are discrete and named — use Stepper.

Playground

Adjust the controls to preview live prop combinations. The code snippet updates to match the current selection.
Uploading
40%
Label placement
labelPlacement
size
size
Indeterminate
isIndeterminate
<Progress labelPlacement="right" />

Example

Linear
No read-out
Read-out on the right
Uploading
40%
Read-out below
Uploading
62%
Floating read-out
62%
Formatted units
Uploading
40 of 100 MB
About 20 seconds remaining
Indeterminate (no value announced)
Loading
Custom range (minValue/maxValue)
Dosage (50–200 mg)
50%
Sizes (sm, md, lg)
Circular
Full ring
25%
62%
90%
Half gauge
62%
Rich centre content
12 GB
of 20 GB
With a label
62%
Storage used
Indeterminate ownership
Indeterminate progress lives here rather than in `Spinner`. This control means “a known task is running”; `Spinner` means “content is not ready yet” and carries no task semantics. The indeterminate bar shows a partial sweep rather than animating, so it stays legible under reduced-motion preferences.

Anatomy

The parts that compose this component.
1
Track
The unfilled rail, or the ring background.
2
Fill
The completed portion.
3
Label
Task name, above the track.
4
Read-out
The value, placed right, bottom, or floating over the fill.
5
Helper text
Supporting detail beneath.

Guidelines

Do
Show the value with labelPlacement when the number itself is useful — a bare bar tells you little.
Do
Use formatValue for units people recognise ("40 of 100 MB") rather than a bare percentage.
Do
Use isIndeterminate rather than a fake creeping value when duration is genuinely unknown.
Don't
Use Progress purely for decoration — it announces as a progressbar and implies a real task.

Props

Public props for this component. Required props are marked; defaults reflect the component implementation.
value
number
Within [minValue, maxValue]. Omit for an indeterminate bar.
minValue / maxValue
number
default: 0 / 100
Range `value` is normalized against — the fill is (value − minValue) / (maxValue − minValue), clamped to 0–100%.
isIndeterminate
boolean
default: false
Unknown duration. Announces busy and publishes no value.
labelPlacement
'none' | 'right' | 'bottom' | 'floating'
default: 'none'
Where the value read-out sits.
formatValue
(value: number) => string
Formats the read-out and the announced value text.
label / helperText
ReactNode
Task name and supporting detail.
size
'sm' | 'md' | 'lg'
default: 'md'
Track thickness.
ProgressCircle: shape
'full' | 'half'
default: 'full'
Complete ring, or a half-circle gauge.
ProgressCircle: children
ReactNode
Centre content, replacing the default percentage.
color / trackColor
ThemeColor
default: 'bgBrandSolid' / 'bgTertiary'
Fill and rail colors, from the semantic token set.
aria-label
string
Accessible name when there is no visible label, or when the label is not plain text.

Deviations from Untitled UI

Where this component deliberately differs from Untitled UI's API, and why. These are kept choices, not gaps to close.
Untitled UI
`labelPosition` / `labelFormatter` / `valueFormatter`
Ours
`labelPlacement` / `formatValue`
Kept: an internally consistent pair shared by Slider, Progress, and ProgressCircle. A cross-library rename would churn three APIs for cosmetic parity.

Accessibility

Roles and screen-reader behavior this component provides, plus keyboard interactions.
Announces accessibilityRole="progressbar" with min, max, and now — the previous implementation had no role or value at all.
Indeterminate publishes no value: reporting 0 would announce "0 percent" for progress that is simply unknown. It reports busy instead.
Values clamp to 0–100, so an out-of-range input never announces something impossible.
react-native-web drops accessibilityValue, so aria-value* are emitted explicitly for the DOM.
The circular form is drawn with stroke-dash rather than rotation, so no animation is needed to represent a value — which matters under reduced motion.
Not interactive. Progress reports state; it does not accept input.

Platform & RTL

Behavior that differs across the platforms this component runs on.
Web
Emits role="progressbar" with aria-valuenow, or aria-busy when indeterminate.
iOS
VoiceOver announces the progressbar trait and its value.
Android
TalkBack announces the progressbar trait and its value.
RTL
The fill grows from the start edge, mirroring with the writing direction.

State coverage

Interaction and visual states this component supports.
base
semantic color
density

Token references

Semantic design tokens this component page exercises. Tap one to edit it live.