Progress reports aria-valuenow instead of staying indeterminate (WEB-1381)
Version: 0.9.1 ยท Type: ๐ Bug Fix
Problemโ
Progress rendered a visually correct bar but was permanently announced as an indeterminate progressbar. Whatever value the caller passed, the element carried data-state="indeterminate" and no aria-valuenow, so a screen-reader user heard "progress bar" with no percentage.
Root causeโ
ui/progress.tsx destructured value out of props and used it only for the Indicator's translateX transform. It was never forwarded to ProgressPrimitive.Root, and the {...props} spread could not restore it because value had already been pulled out of the rest object.
Radix derives aria-valuenow, aria-valuetext, data-state and data-value from Root's value; with value === undefined it falls back to null, i.e. indeterminate. The visual stayed correct precisely because it is computed independently of the Radix context โ which is why the defect survived review and screenshots.
Changed Filesโ
packages/design/src/components/ui/progress.tsxpackages/design/src/components/Progress/__tests__/Progress.test.tsx
Changesโ
progress.tsx: passvaluetoProgressPrimitive.Rootin addition to the Indicator transform.Progress.test.tsx: assertrole,aria-valuenowandaria-valuemin/aria-valuemaxfor 0 / 42.5 / 100, plus a case pinning that a bar rendered withoutvaluestill stays indeterminate.
Impactโ
| Attribute | Before | After |
|---|---|---|
aria-valuenow | absent | present when value is valid |
aria-valuetext | absent | present (getValueLabel, default rounds to a percentage) |
data-state | always indeterminate | loading / complete |
data-value | absent | the numeric value |
- Visual output is unchanged. The Indicator transform never depended on the Radix context.
role="progressbar"is not new โ Radix always rendered it; only the value semantics were missing.- Radix now validates
valueand logs aconsole.errorfor a non-number / NaN /< 0/> max. Rendering is unaffected in that case (Radix falls back to indeterminate while the visual still comes from the component's own transform), so this surfaces pre-existing caller bugs rather than creating new ones. - If you rely on
data-state="indeterminate"in a selector, note it now only applies to bars rendered without avalue.
Labelling the barโ
Two things are worth pairing with this fix on the consumer side:
- Give the bar an
aria-label. Aprogressbarwith no accessible name is announced as an anonymous one. - If the percentage you print next to the bar is not simply
Math.round(value), overridegetValueLabel.aria-valuetexttakes precedence overaria-valuenowin ARIA, and Radix's default label rounds โ so a bar that displays a floored66%would otherwise be spoken as67%.