Progress

How far along something is: a line, a circle, a dashboard gauge or a set of steps, with a status the color follows.

Accessibility

Progress gets its role and value attributes for free. The name, the readable text and the announcements are up to the page around it.

What Progress renders

The root is a plain <div> carrying role="progressbar", aria-valuenow, aria-valuemin={0} and aria-valuemax={100}. The track, the rail and the value text are decoration.
  • aria-valuenow is a whole number, so 40.7 is announced as 40.
  • With success.percent set, the announced value is the inner run, so a bar drawn at 80 with a success run of 30 announces 30. Give a two-run bar its real state in text.
  • There is no aria-valuetext, so a bar labeled “3 of 8 files” still announces 37. Put the unit in the text around it.
  • aria-label and aria-labelledby reach the root, and they are the only way to name it. A progress bar with no name is announced as a bare number. Point aria-labelledby at the heading that says what is loading.
  • The value text gets a title when it is a string. It only shows on hover and is not a name, so nothing should depend on it.

The number has to be readable as text

A value that only exists as an ARIA attribute is one most readers never see. Three props can hide the visible number from everyone.
  • showInfo={false} removes the value text, so only a screen reader gets the number. Put it in text next to the bar.
  • format replaces the value text and the status icon. A format that returns an icon or nothing leaves the bar with no reading.
  • A circle of 20px or less moves its value into a tooltip. Hover does not exist on touch and is hard with a tremor, so give a tiny circle its number somewhere else too.
  • When the label uses another unit, the text beside the bar is what counts. Write the sentence a reader would say (“Uploading 3 of 8 files”).

Announcing progress without flooding

Progress announces nothing by itself: there is no aria-live, and a changing aria-valuenow is not reliably spoken. The page around it announces, and the usual mistake is announcing too much.
  • Do not wrap a moving bar in aria-live="polite". Every change is queued, and the reader hears nothing else until the upload ends.
  • Announce milestones: it started, it is a quarter done, it finished, it failed. Use a separate polite live region with one short sentence, updated now and then.
  • aria-busy on the region that is filling says “not ready yet” without any announcements.
  • Say something at the end. A bar that reaches 100 and vanishes tells a screen reader user nothing, and a failed run needs its reason spoken.

Color is never the only signal

status="success" and status="exception" differ by color and by one icon. Without the icon, two opposite outcomes look the same in grayscale and to a reader who cannot tell the colors apart.
  • The check and the cross come from your icon pack.
  • The icon only shows when showInfo is on, no format is set and the value is not inside the bar. A failed bar with a custom format shows its failure in color alone.
  • Pair any status with words. “Import failed: 4 rows rejected” beside the bar means the red is not the only signal.
  • status="active" is not a signal either. It is decoration on a bar, and circles do not show it.

The bar against its rail

The information is the edge between the filled run and the track behind it, so that pair needs 3:1 contrast under WCAG 1.4.11.
  • Both colors come from the theme. The default theme passes, but a pale custom colorPrimary can fall under 3:1 against the track, so check yours.
  • Check a gradient at both ends against the track, especially the light end.
  • An inner value sits on the filled run. The text color is picked from the first stroke color only, so check it across a gradient.
  • Check railColor against the page too. A track that blends into the card makes a bar at 20% look like a bar of unknown length.

Motion

status="active" sweeps a shimmer across the bar without end, and it does not check for reduced motion.
  • WCAG 2.2.2 covers anything that moves for more than five seconds. Set the status back to normal as soon as the work stops, and avoid active on a screen the reader has to stay on.
  • The kit's animation of the value text is behind motion-safe:, so readers who ask for less motion do not see it.
  • The bar's growth is a transition, and it turns off with the theme's motion token.
  • Do not let percent go backwards. A bar that retreats reads as an error even when nothing failed.