ComponentsFeedback

Progress

Shows how much of a task is done, such as an upload or storage that fills up. When the amount is not known yet, pass null and the bar slides. For a short wait with no amount, use Spinner.

States

Empty, partial, complete, and indeterminate. A null value has no amount, so the value text stays empty and the indicator slides.

Progress
x
Progress
x
Complete
x
Waiting
x

Storage use

A bare bar named with aria-label, beside text that gives the real numbers. Use over the quota is capped at 100.

Upload list

A bar under each file that is sending, named after the file. getAriaValueText reads the sizes instead of a bare percent, and “Saving…” once every byte is sent.

Uploading 2 of 4…

  • Q3 board deck.pdf

    Uploading 3.1 MB of 8.4 MB · Reports

    x
  • Team offsite photos – Lisbon 2026 – final selects.zip

    Saving… · Shared / Events

    x
  • Interview notes.docx

    Waiting · Research

  • logo-dark.png

    Uploaded · Brand

Usage

Progress usageTSX
import {
  Progress,
  ProgressLabel,
  ProgressValue,
} from "@tulipfarm/sprout/progress";

<Progress value={40}>
  <ProgressLabel>Progress</ProgressLabel>
  <ProgressValue />
</Progress>

<Progress value={null}>
  <ProgressLabel>Waiting</ProgressLabel>
  <ProgressValue />
</Progress>

Progress renders the track and indicator for you. Its children sit on one row above the track: ProgressLabel at the start andProgressValue at the end. The row gap is 8px and the track is 4px tall.

  • value: a number from min tomax (0–100 by default), or null when the amount is unknown.
  • format and locale change how the value is written.ProgressValue also takes a function child.
  • ProgressTrack and ProgressIndicator are the partsProgress uses. Use them only to build your own bar.

Every part takes className (a string or a function of the state) and a Base UI render prop. The root, track, indicator, label, and value get data-progressing,data-complete, or data-indeterminate.

Bare bar

Name it with aria-label and read sizesTSX
<Progress value={Math.min(100, percent)} aria-label="Workspace storage used" />

<Progress
  value={Math.floor((sent / size) * 100)}
  aria-label={`Uploading ${name}`}
  getAriaValueText={() => `Uploading ${formatFileSize(sent)} of ${formatFileSize(size)}`}
/>

With no children, the bar stands alone. Keep the real numbers in text next to it, and name it with aria-label.

Accessibility

The root is a progressbar with aria-valuemin,aria-valuemax, and aria-valuenow. AProgressLabel names it; otherwise passaria-label. getAriaValueText replaces the percent that a screen reader reads, for example with “Uploading 3.1 MB of 8.4 MB”. ProgressValue is hidden from screen readers, because the bar already reads the value.

While the value is null, aria-valuenow is left out. The width changes in 180ms. With reduced motion, the width jumps and the sliding bar becomes a faint full track.

Theme tokens

The track uses --sprout-secondary and the indicator--sprout-primary, so the bar takes the brand color in the TulipFarm theme. The label uses --sprout-text and the value--sprout-muted. In forced colors, the indicator isHighlight on an outlined track.