Skip to content

Progress

Stable

Shows how far a task has come, or that it is underway when its length is unknown.

Uploading assets
x

Anatomy

Uploading assets
x
  1. 1LabelSays what is in progress. It also names the bar for screen readers.
  2. 2TrackThe whole task, in a muted tint.
  3. 3ValueThe percentage, in tabular figures so it doesn't jitter as it changes.
  4. 4IndicatorThe completed share. It animates between values, and sweeps across the track when the value is unknown.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/progress.json

Usage

import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"
<Progress value={64}>
  <ProgressLabel>Uploading assets</ProgressLabel>
  <ProgressValue />
</Progress>

Progress renders the track and indicator for you. Children — typically a label and a value — sit on the line above the bar.

Examples

Sizes

sm (4px) fits inside lists and table cells, default (6px) suits most forms and cards, and lg (10px) is for a progress bar that is the focus of the view.

Small
x
Default
x
Large
x

Indeterminate

Pass value={null} when you know work is happening but not how much remains. A segment sweeps across the track until you switch to a determinate value.

Preparing your export…
x

Live updates

The indicator animates between values with the slow duration and standard easing, so frequent updates read as smooth motion rather than jumps.

Building…
x

File uploads

In lists, use the sm size and let the file name be the label. Swap the value for a confirmation when an item finishes, so completed rows stop drawing the eye.

  • brand-guidelines.pdfDone4.2 MB
    x
  • hero-illustration.png2.8 MB
    x
  • product-walkthrough.mp4148 MB
    x

Custom value

ProgressValue accepts a render function for units other than percent. When you change the visible format, give assistive technology the same words with getAriaValueText.

Storage
x
<Progress
  value={7.2}
  max={10}
  getAriaValueText={() => "7.2 of 10 gigabytes used"}
>
  <ProgressLabel>Storage</ProgressLabel>
  <ProgressValue>{() => "7.2 GB of 10 GB"}</ProgressValue>
</Progress>

Guidelines

When to use

  • For tasks that take longer than about a second and report measurable progress: uploads, exports, imports, builds.
  • For quantities against a limit — storage, seats, quota — where the bar is a gauge rather than a timer.

When not to use

  • For short or unmeasurable waits inside a control — use a Spinner or the Button's loading state.
  • For content that is still loading — use a Skeleton that previews the layout.
  • For steps in a flow. A progress bar measures work, not navigation.

Skeleton, spinner or progress?

WaitUseWhy
Under ~1 secondNothingIndicators that flash for a moment feel slower than none.
Content is on its waySkeletonShows the shape of what's coming, so the page doesn't jump.
An action is in flightSpinner or Button loadingConfirms the system heard the request.
Measurable work, over ~1 secondProgressTells people how long they'll wait, so they can decide to stay.

Be honest

Never let a determinate bar stall at 99% or jump backward. If you can't estimate progress reliably, use the indeterminate state and say what's happening in the label.

Importing 1,280 contacts
x
Do.The label says what is happening, the value says how far along it is.
x
Don’t.A bar without a label forces people to guess what is loading.

Accessibility

  • Renders role="progressbar" with aria-valuemin, aria-valuemax and aria-valuenow. The indeterminate state omits aria-valuenow, which screen readers announce as busy.
  • ProgressLabel is wired to the bar with aria-labelledby. When you can't show a label, pass aria-label to Progress instead — a progress bar must always have a name.
  • aria-valuetext is generated from the formatted value. Override it with getAriaValueText whenever the visible value isn't a percentage.
  • Progress bars aren't live regions: screen readers read the value when people navigate to it, not on every change. Announce milestones — Upload complete — with a Toast or a role="status" message.
  • Under prefers-reduced-motion, value changes and the indeterminate sweep stop animating.

API reference

Progress

The root. Renders a <div> containing your children followed by ProgressTrack and ProgressIndicator. Accepts every prop of Base UI's Progress.Root.

PropTypeDefault
valuerequired

The current value. null renders the indeterminate state.

number | nullNo default
min

The minimum value.

number0
max

The maximum value.

number100
size

Track height: 4px, 6px or 10px.

"sm" | "default" | "lg""default"
format

Formats the value shown by ProgressValue and announced to screen readers.

Intl.NumberFormatOptionsNo default
locale

Locale for number formatting. Defaults to the user's runtime locale.

Intl.LocalesArgumentNo default
getAriaValueText

Returns a human-readable alternative for the current value.

(formattedValue: string, value: number | null) => stringNo default

ProgressLabel

The accessible name of the bar. Renders a <span>.

PropTypeDefault
className

Additional classes.

stringNo default

ProgressValue

Displays the formatted value, right-aligned. Renders a <span>.

PropTypeDefault
children

Custom rendering for the value, e.g. units other than percent.

(formattedValue: string | null, value: number | null) => ReactNodeNo default

ProgressTrack and ProgressIndicator

Rendered by Progress automatically. They are exported for custom compositions — for example, a segmented meter that renders several indicators.