Progress

Shows how far a task has got, or how full something is.

Examples

A task

Nightly eval
x

A usage meter

Tokens this hour
x

A list of uploads

help-center.zipRunning
62%
x
4.1 of 6.6 MB
past-tickets.csvFailed
Connection reset
refund-policy.pdfDone
18 KB

Setup steps

Mode

  • x

    progressdefault

  • x

    meter

Shape

  • x

    bardefault

  • x
    x

    ring

Kind

  • x

    run

  • x

    deploy

Status

  • x

    warn

  • x

    fail

Usage

When to use it
Use
A task runs for more than a second and has a known or unknown amount (progress).
Use
A level sits inside a range that can go down: usage, seats, storage (meter).
Use
A small wait sits in a row, cell, toast or line of text (ProgressRing).
Use
A flow has ordered steps with the current one marked (ProgressSteps).
Not for
Content is loading and its shape is known. Use Skeleton.
Not for
A one-time result to report. Use Toast.
Not for
A single number with a label and no range. Use Stat.

Rules

9 rules
Must
Pass value={null} while the amount is unknown and switch to the number when it arrives. Because value={0} looks stalled and a screen reader announces 0 percent, while null shows the sliding segment and reads as busy.
Never
Animate a bar toward 90% on a timer. Use value={null} until the real amount is known. Because the bar stops at 90% whether the job takes a second or an hour, so the person stops trusting every bar.
Must
Use mode="meter" for a level that can go down (usage, seats, storage). Because mode="progress" is a progressbar for a task moving to done, so usage that drops reads as a job going backward and gets no warnAt notch.
Must
Use ProgressRing at 12 or 16px inside a row, cell, toast or line; label={null} when a nearby word says what waits. Because the bar needs a full-width row and pushes the line apart, while a 12px ring sits in the text height.
Must
Pass aria-label when a Progress has no ProgressLabel. Because Base UI renders an unnamed progressbar, so a screen reader reads "62 percent" with no task.
Should
Name the task or measure with its object: "Indexing docs-v3", "Tokens this hour".
Should
Say the amount in the thing's unit when it has one, and pass the same text to getAriaValueText; use a percent only without a unit.
Must
Keep the sr-only word the step and item parts render ('done', 'failed', 'waiting on you'); never replace it with only a glyph. Because a check or cross alone is silent to a screen reader, so a failed step sounds like any other.
Never
Use three bouncing dots. Use ProgressRing. Because dots carry no role or name and keep bouncing under reduced motion; ProgressRing does both.

Accessibility

5 checks
progress is role="progressbar" and meter is role="meter", named by ProgressLabel or aria-label.
An indeterminate bar has no aria-valuenow; a third of the track slides across it, and it rests at the start under reduced motion.
Step triggers and item Cancel or Retry are buttons named with their item; later steps are disabled when linear.
Step and item status render an sr-only word, so status is not conveyed by shape or hue alone.
ProgressRing with label={null} is hidden from assistive tech.

Edge cases

10 edge cases
Value over max or under min
Clamps to full or empty.
max of 0
Divides by 1 and shows 0, never NaN.
Value 1 of 100
A 1% sliver, at least 1px.
Long label
Truncates with an ellipsis; the value keeps its place.
Failed upload item
Keeps its last amount, swaps Cancel for Retry and shows Failed in fail text.
Indeterminate for a long time
Runs without changing the layout.
Updates every 50ms
The fill eases over --dur-base and restarts from where it is; no jitter.
Right to left
The fill starts at the inline start.
Zoom 200%
The bar stays 4px; labels grow and wrap.
Inside a Button
Use ProgressRing.

API

12 props
mode
'progress' | 'meter' default 'progress'
value
number | null default null
shape
'bar' | 'ring' default 'bar'
size
'sm' | 'default' | 'lg' default 'default'
min, max
number default 0, 100
kind
Kind
status
'warn' | 'fail'
warnAt, failAt
number | null default 0.8, 1
ProgressRing size, label, fraction
12 | 16 | 20 | 24, string | null, number default 16, "Loading", null
ProgressSteps value, onValueChange, orientation, linear
number, fn, h | v, boolean default horizontal, true
ProgressStep state
'done' | 'current' | 'upcoming' | 'error' | 'pending' | 'waiting' default from value
ProgressItem title, status, value, detail, onCancel, onRetry
ReactNode, 'queued' | 'running' | 'done' | 'warn' | 'failed' | 'canceled', number | null, ReactNode, () => void, () => void

Tokens

23 tokens
--cell-quiet
oklch(var(--ink-c) / 0.07)
--accent-line
var(--ink)
--warn-line
oklch(62% 0.132 68)
--fail-line
oklch(60% 0.236 26)
--accent-mark
var(--ink)
--accent-wash
var(--surface-selected)
--text-3
oklch(var(--ink-c) / 0.66)
--ink
oklch(var(--ink-c))
--line-frame
oklch(var(--ink-c) / var(--line-frame-alpha))
--line-control
oklch(var(--ink-c) / 0.16)
--line-row
oklch(var(--ink-c) / 0.08)
--surface-inverse
var(--ink)
--text-inverse
var(--paper)
--radius-inner
4px
--radius-soft
6px
--radius-frame
10px
--warn-text
oklch(49% 0.100 68)
--fail-text
oklch(51% 0.180 26)
--type-label-sm
400 12px/16px var(--font-sans)
--type-tag
var(--type-code-sm)
--dur-base
160ms
--dur-spin
800ms
--ease-spin
linear

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.