Progress
Shows how far a task has got, or how full something is.
Examples
Nightly eval
Tokens this hour
help-center.zipRunning
62%
past-tickets.csvFailed
Connection reset
refund-policy.pdfDone
18 KB
Mode
progressdefault
meter
Shape
bardefault
ring
Kind
run
deploy
Status
warn
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'valuenumber | nulldefaultnullshape'bar' | 'ring'default'bar'size'sm' | 'default' | 'lg'default'default'min, maxnumberdefault0, 100kindKindstatus'warn' | 'fail'warnAt, failAtnumber | nulldefault0.8, 1ProgressRing size, label, fraction12 | 16 | 20 | 24, string | null, numberdefault16, "Loading", nullProgressSteps value, onValueChange, orientation, linearnumber, fn, h | v, booleandefaulthorizontal, trueProgressStep state'done' | 'current' | 'upcoming' | 'error' | 'pending' | 'waiting'defaultfrom valueProgressItem title, status, value, detail, onCancel, onRetryReactNode, '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