Skip to content

ComponentsFeedback

Progress Bar

Shows the progress of an operation over time, as a bar or a circle. Determinate or indeterminate, with a visible label, locale-aware value formatting, custom value text, three sizes and seven colors.

React AriaSource
Uploading files18%

Installation

pnpm dlx shadcn@latest add @desyne/progress-bar

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { ProgressBar, ProgressCircle } from "@/components/ui/progress-bar";
tsx
<ProgressBar label="Uploading files" value={40} />
<ProgressCircle aria-label="Syncing" value={60} />

Animation keyframes

The indeterminate bar uses the animate-indeterminate keyframes from the theme CSS, which the registry adds on install. The circle's indeterminate state uses Tailwind's built-in animate-spin.

When to use

  • Progress Bar — a task that is running and will finish: uploads, imports, exports, multi-step setup. Use isIndeterminate while the duration is unknown.
  • Meter — a level within a known range that isn't "progressing": storage used, quota, password strength.
  • Spinner — a short, unmeasured wait in a small space, such as inside a button or badge.
  • Skeleton — while the layout of content that is about to appear is known.

Anatomy

tsx
<ProgressBar label="…">   {/* role="progressbar" */}
  {/* header: Label + value text */}
  {/* track */}
  {/*   fill (width = percentage) */}
</ProgressBar>

<ProgressCircle>           {/* role="progressbar" */}
  {/* svg: track circle + fill circle */}
  {/* centered value text (md and lg) */}
</ProgressCircle>
PartRendersNotes
ProgressBar<div role="progressbar">React Aria ProgressBar. Sets aria-valuenow, aria-valuemin, aria-valuemax and aria-valuetext.
LabelLabel (<span>)Rendered when label is set and wired up as the accessible name.
Value text<span>The formatted value (or valueLabel). Hidden when showValue={false} or indeterminate.
Track<div>Full-width, rounded, tinted with the tone at 15%.
Fill<div>Width follows the percentage with a 500ms transition; slides back and forth when indeterminate.
ProgressCircle<div role="progressbar">Same semantics; renders an aria-hidden SVG ring and an optional centered value.

Examples

Sizes

md (8px) is the default. Use sm (4px) inside cards, lists and dialogs, and lg (12px) when the bar is the main content of the view.

Small30%
Medium55%
Large80%

Colors

brand is the default. Switch to success when a task completes, and to warning or danger when it stalls or fails, so the state is visible at a glance.

Syncing contacts45%
Indexing documents62%
Backup complete100%
Retrying upload38%
Migration paused71%

Indeterminate

Set isIndeterminate when you can't measure progress yet, such as while connecting or analyzing. The value text is hidden and aria-valuenow is removed, so screen readers announce it as busy rather than 0%.

Connecting to GitHub…

Custom value text

valueLabel replaces the percentage with your own text, like "3 of 12 files". It's also used as aria-valuetext, so pass a string. Combine it with maxValue to work in your own units.

Uploading photos3 of 12 files
Course progressLesson 7 of 10

Formatting and ranges

minValue and maxValue (0 and 100 by default) set the range. formatOptions takes any Intl.NumberFormatOptions: the default percent style formats the percentage, every other style formats the raw value in the user's locale.

Downloading update348 MB
Fundraising goal$18,450
Rendering33.3%

Without a visible label

When the label lives elsewhere in your layout, point to it with aria-labelledby (or pass aria-label) and hide the built-in value with showValue={false}.

Set up your workspace2 of 5 steps

Live updates

Update value as work progresses; the fill animates between values. Change label and color to reflect the final state.

Exporting report…0%

Progress circle

ProgressCircle shows the same information in a compact ring: sm (20px) for inline status, md (48px) and lg (80px) for dashboards and cards. It has no built-in label, so aria-label or aria-labelledby is required. The percentage appears in the center at md and lg. It has its own page, Progress Circle, with more sizes and custom center content.

82%
64%

Indeterminate circle

With isIndeterminate, the ring becomes a spinning quarter arc.

Circle with value text

valueLabel works for circles too; keep it short enough to fit the ring. Pair small circles with a nearby text label.

6/8

Weekly goal

6 of 8 workouts

Syncing 2 of 5 calendars

Recipes

Upload list

Per-file progress in a list. Each bar has its own aria-label, turns danger on failure, and is replaced by a check icon once the upload finishes.

Uploads

  • Q3-report.pdf2.4 MB
  • team-offsite.jpg20%
  • product-demo.mp45%

Data import

Start indeterminate while the file is analyzed, then switch to a determinate bar that counts rows with valueLabel.

customers-2024.csv

1.8 MB · 4,800 rows

Onboarding checklist

A large ProgressCircle driven by the number of completed steps, using maxValue so the ring maps directly to the checklist.

25%

Get started

3 steps left

Accessibility

  • Renders role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax and a formatted aria-valuetext (your valueLabel, or the value formatted with formatOptions).
  • Indeterminate bars omit aria-valuenow and aria-valuetext, so they're announced as busy.
  • Every progress bar needs an accessible name: label, aria-label or aria-labelledby. ProgressCircle has no label prop, so always pass one of the aria props.
  • Progress bars are not live regions; screen readers read the value when the user reaches it. Announce completion separately (for example with a toast) if it matters.
  • Respects prefers-reduced-motion. The fill jumps to each new value instead of animating. The indeterminate bar stops sliding and becomes a full-width bar that fades gently in and out, so it never looks like a partial value, and the indeterminate circle stops spinning and fades the same way. Screen readers get the same aria-valuenow / aria-valuetext (or no value when indeterminate) in both modes.

Styling

Data slots

SlotElement
data-slot="progress-bar"ProgressBar root
data-slot="progress-circle"ProgressCircle root
data-slot="label"The visible label

Render props

className accepts a function of the render state, which exposes percentage, valueText and isIndeterminate:

tsx
<ProgressBar
  label="Upload"
  value={value}
  className={({ percentage }) => (percentage === 100 ? "opacity-60" : "")}
/>

Indeterminate bars have no aria-valuenow, so you can also target them with [&:not([aria-valuenow])]:….

Tone variables

color sets --tone on the root. The track uses --tone at 15% and the fill uses it at full strength, so a custom color only needs one variable:

tsx
<ProgressBar label="Rendering" value={40} className="[--tone:var(--color-violet-600)]" />

API Reference

ProgressBar

Prop

Type

Also accepts every prop of React Aria's ProgressBar, such as id, style and aria-describedby. The children are rendered by the component and can't be replaced.

ProgressCircle

Prop

Type

Also accepts every prop of React Aria's ProgressBar.

  • Meter — a level within a range, like storage used.
  • Spinner — a compact indeterminate indicator.
  • Skeleton — placeholders while content loads.
  • Toast — announce when a long task finishes.