Skip to content

Progress

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/progress.json
See it liveA known fraction, an unknown one, and the tones.→

Two states, and collapsing them is the defect this exists to prevent:

  • determinate — the fraction is known, the bar fills to it, and the reader can tell how long is left.
  • indeterminate — something is happening and nobody knows how much is left. The bar says exactly that, by pulsing rather than filling.

Collapsing them means picking a number that is not true. A bar that creeps to 90% and waits there is the commonest version, and it is a lie the reader learns to distrust — after which no progress bar in the product means anything. value={undefined} is the honest answer, and it is the default.

<Progress value={done / total * 100} label="Uploading">Uploading</Progress>
<Progress label="Working">Working</Progress> {/* no number claimed */}

No percentage is shown when there is no percentage. “0%” for an unknown amount is the same lie as the bar that creeps.

The indeterminate bar is striped, not filled. A full-width fill was the first drawing of it and was wrong in the way that matters: measured on the stand it painted 384px of a 384px track, and a reader glancing at that sees “done” — the opposite of the state. Pulsing did not rescue it, and under prefers-reduced-motion the pulse stops and a complete-looking bar is all that is left.

Stripes cannot be read as a fraction at all: there is no edge to mistake for a boundary. They are a gradient rather than an animation of their own, because a @keyframes name in the theme is a contract the line carries forever — and this needs no movement to say what it says.

label is required. A bar with no name is announced as a percentage of nothing, and the word belongs to the product.

Not a Spinner. A spinner says “working” in a corner; this says “working, and here is the shape of it” across a width. Where the shape of what is coming is known, Skeleton says more than either.

Props

Read from the types in ui/progress.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
sizesm | md'md'
toneaccent | good | warn | bad'accent'
valuenumber | nullThe fraction done, 0 to max. Leave it out when it is not known - that is not a missing value but a different, honest state.
maxnumber100The maximum value.
labelrequiredstringWhat is progressing, for a screen reader. Required: a bar with no name is announced as a percentage of nothing, and the word belongs to the product.
classNamestring
childrenReactNodeA visible label beside the bar, when there is room.

And every prop of Base UI's Progress.Root except className, value, passed on.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 16 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/progress.tsx - the file a product copies - newest first.

  1. v0.23.0
    • fix stripe the unknown bar, because a full one reads as done 1eeccf2
    • feat the screens between asking for data and showing it bc3f636