State & Feedback Systems

Progress Bar

A spring-tweened linear progress bar with a rolling percentage, an indeterminate sweep and buffer layer, and a single pulse with a check mark on reaching 100%.

A linear progress bar with some spring to it. When progress changes, the indicator springs to its new position and the percentage on the right rolls digit by digit. When the total is unknown, switch to indeterminate mode and a 40%-wide bar sweeps back and forth along the track; for video or streaming, add a buffer layer one shade lighter in the same colour. The moment progress goes from below max to max, the whole track pulses once, the right-hand text cross-fades into a check mark, and onComplete is called.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add progress-bar.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

42
120
Active edited usageDefault props
<ProgressBar/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add https://webberui.com/r/progress-bar.en.json

Or, once registries are configured in components.json, install it as @webberui/progress-bar.

Usage

import { ProgressBar } from "@/components/ui/progress-bar";

<ProgressBar
  value={uploaded}
  max={total}
  label="design-v3.fig"
  showLabel
  onComplete={() => console.log("Upload complete")}
/>

{/* Indeterminate */}
<ProgressBar indeterminate label="backup-archive.zip" />

{/* Video playback + buffer */}
<ProgressBar value={played} buffer={buffered} size="sm" />

Props

PropTypeDefaultDescription
valuenumber | null0Current progress (0..max, clamped); null is treated as indeterminate
maxnumber100Maximum value
indeterminatebooleanfalseIndeterminate mode: shows a back-and-forth sweep and omits aria-valuenow
buffernumber—Buffered progress (0..max), drawn as a second bar in the same colour at 40% opacity
showLabelbooleanfalseShow the percentage to the right of the track
labelReactNode—Top-left label text (e.g. a file name), also used as the progressbar's accessible name
formatLabel(percent: number) => string—Custom percentage text; when provided it renders as plain text without digit rolling
size"sm" | "md" | "lg""md"Track height: sm 4px, md 8px, lg 12px
colorstring—Indicator colour; defaults to --wb-progress-bar-color (then the neutral scale)
stiffnessnumber120Spring stiffness for the width and digit tweens
dampingnumber20Spring damping for the width and digit tweens
showCompletebooleantruePulse once and swap the right-hand text for a check mark on reaching 100%
onComplete() => void—Called when progress goes from below max to max (fires again after dropping and refilling)
classNamestring—Passed through to the outermost container

How it works

  • The indicator animates scaleX (with transform-origin: left) rather than width, so the tween never triggers layout.
  • Digit rolling: each digit of the percentage is a 1em-tall overflow: hidden column holding a 0–9 stack, moved to the target by a spring y; when the digit count changes (9% → 10%, 99% → 100%) the new digit fades in, and tabular-nums keeps the width steady.
  • The indeterminate bar is 40% wide and sweeps back and forth along the track over 1.4 seconds with easeInOut.
  • The completion pulse fires once on the below-max → max transition; mounting already full does not pulse, while dropping and refilling fires it again.
  • Colour: set the global CSS variable --wb-progress-bar-color to restyle every bar, and the color prop overrides it; the buffer layer's transition length reads --wb-progress-bar-duration on the root element (300ms by default).

Accessibility

  • The root is role="progressbar" with aria-valuemin, aria-valuemax, aria-valuenow and aria-valuetext (e.g. "42% complete"); indeterminate mode omits aria-valuenow, sets aria-valuetext to "Processing" and sets aria-busy
  • With a label, aria-labelledby points at it; without one it falls back to aria-label="Progress"
  • The rolling digits and the check mark are hidden from assistive tech; what gets announced comes from aria-valuetext
  • When the user has reduce motion enabled: the indicator jumps into place without a spring, digits swap instantly, indeterminate mode becomes a static 40% bar that slowly breathes in opacity with a "Processing" label, and completion shows the check mark without the pulse