Progress

How far a task has got: role=progressbar with the value it really has, an indeterminate bar that reports none at all, and no JavaScript on the server path.
Import
JSX
import Progress from '@box-kite/react/components/progress';
Usage
JSX
<Progress label="Upload" value={62} />

No value is a state, not a zero

Leave value out and the bar is indeterminate: aria-valuenow is left off entirely rather than written as 0, because the one thing a reader must not be told is a position nobody measured. The bar sweeps instead.
JSX
<Progress label="Preparing" />
The sweep names its duration in milliseconds, so it sits outside the --transitionTime that prefers-reduced-motion zeroes and stops itself instead — a full-width bar at reduced opacity, which still reads as "working" without anything moving.

A value that moves

The fill is the one thing here that transitions, and it is the width, so a value arriving in steps still travels between them.
20
JSX
<Progress label="Download" value={value} max={100} format={(value) => `${value} per cent`} />

The fill is an inline style, and everything else is a class

A percentage that moves with a download would be a rule per frame if it went into a class name — so the width is an inline inline-size, the exception Slider explains at length, and the track, the colours, both themes and the forced-colors fallback are shared rules.
Where the value is data rather than an animation, use a ring
ProgressRing from components/chart rounds its fraction to half a percent and puts it in a class, which costs nothing and shares between every ring on the page. A dashboard of a hundred figures wants that one.
It renders on a server
No state, no effect, no measurement — so a page paints a real figure before any JavaScript arrives, the way Flex and Button do.

Its own range

min and max are what "full" means, so a count of files needs no arithmetic at the call site. format writes aria-valuetext for a value the bare number does not read as.
JSX
<Progress label="Files" min={0} max={8} value={3} format={(value) => `${value} of 8 files`} />

Naming it

A progress bar has to be named: a percentage nobody can attach to anything is not information. label writes aria-label and labelledBy points at the heading or the text that already says it.

Styling

Two parts: progress, which is the track and carries the role, and progress.fill, whose indeterminate variant is the sweep. The root takes Box props directly, so the height and the radius are props on the element.
JSX
<Progress label="Upload" value={62} height={1} borderRadius={0} />

Box.components({
  progress: { children: { fill: { styles: { bgColor: 'emerald-500' } } } },
});

Progress props

Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on /box rather than repeated here.
PropTypeDefaultWhat it does
valuenumber—How far along it is. Leave it out for a task with no measurable end — the bar sweeps and reports nothing.
minnumber0The bottom of the range. Default 0.
maxnumber100The top of the range, and so what "full" means. Default 100.
labelstring—What the bar is called. A progress bar has to be named — nothing about it is readable otherwise.
labelledBystring—The same, naming an element that already says it.
format(value: number) => string—The value as it should be read out: "3 of 10 files", "eleven minutes left".

Progress accessibility

  • role="progressbar" with aria-valuemin, aria-valuemax and, when there is one, aria-valuenow.
  • label or labelledBy is what names it. A bar with neither is a percentage nobody can attach to anything.
  • format writes aria-valuetext, for a value the number alone does not read as.
Swept with axe on every release, in this state: Progress. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.

Progress style tree

Every part the component draws is a node with a name, so a default can be restyled with Box.components() instead of a selector — and a variant is a name too.
progress
progress.fillvariants: indeterminate