ArenaProgressBar.prompt.md

September 6, 2026 · View on GitHub

Progress bar (H1). Gives visible status to measurable processes outside the splash: deployments, uploads, migrations. Respects prefers-reduced-motion in indeterminate mode.

<ArenaProgressBar label="Deploying build #4821" progressPercentage={64} />
<ArenaProgressBar tone="success" progressPercentage={100} label="Published" />
<ArenaProgressBar indeterminate tone="accent" label="Connecting…" />
<ArenaProgressBar shape="radial" size="lg" progressPercentage={64} label="Lesson 4" />

shape="radial" draws the same meter as a ring: the figure sits in the middle and the label under it. Reach for it where the meter is the tile rather than a line in one, such as a completion ring on a dashboard or a node on a path. Keep the bar for a row.

The middle is a slot, so a ring measures something of yours instead of showing a number. Turn the figure off yourself when you fill it: the two share that space, and no component here decides what it draws from what you projected.

<ArenaProgressBar shape="radial" size="lg" showPercentage={false}
  progressPercentage={40} label="Unit 3, lesson 4">
  <ArenaIconButton icon="ph-fill ph-star" label="Start lesson 4" />
</ArenaProgressBar>

Members, in contract order and under this layer's own names. * marks a required one.

MemberFormTypeDefaultWhat it is
childrenslotWhat sits in the middle of a ring, in place of the percentage: a glyph, a mark, or the control the ring measures. A bar has no middle, so a bar draws nothing for it. The ring's own progressbar element is the drawing rather than the box around it, because that role's children are presentational and a control projected inside it would be drawn and never announced; here it is a sibling of the meter and keeps everything it came with.
progressPercentageprimitivenumber0How far along, 0-100. Clamped and rounded. Ignored when indeterminate.
indeterminateprimitivebooleanfalseA wait with no percentage; the bar sweeps instead of filling.
toneenumArenaProgressTone"accent"The bar's colour.
label*primitivestringNames what is progressing. Drawn above the bar or under the ring, and it is the meter's accessible name. Required and guarded rather than defaulted: nothing can derive what is progressing, and a fallback of "Progress" satisfies roles.label mechanically while telling a screen-reader user only what the component is -- two of them on one page announce identically.
showLabelprimitivebooleantrueDraws the label beside the meter. False leaves the meter alone and keeps the accessible name, which is carried by aria-label on the progressbar element rather than by the text. For a bar in a table cell or a card row, where the row already names what is progressing and repeating it is noise. label stays required either way, on the reading it already carries: what a screen reader announces is not a decision about what is drawn. It is the same escape ArenaIconButton.showLabel offers, and it is here because the two components pose one question.
showPercentageprimitivebooleantrueShows the percentage: beside the label on a bar, and in the middle of a ring, which is the figure a meter in a tile is read by. Determinate only. Turn it off when content fills a ring's middle: the two share that space, and Arena never derives what it draws from what a consumer projected, because projected content is not inspectable in at least one layer.
sizeenumArenaControlSize"md"How heavy the meter is: the bar's thickness, and a ring's diameter with a band the same weight as the bar it replaces.
shapeenumArenaProgressShape"linear"Whether the meter is drawn as a bar or as a ring. A ring puts the percentage inside its own track and the label under it, which is the arrangement a tile wants and the one a row cannot give: a bar is as wide as its row and reads along it, while a ring is as wide as it is tall and reads at a glance. It is a shape rather than a second component because everything else is the same question answered once: the percentage, the tone, the required name, the announcement and the sweep a wait draws.

progressPercentage is 0–100, clamped and rounded; it is not a form control's value, which is what that name means everywhere else in this library. showPercentage (default true) shows the number beside the label; it is drawn in determinate mode only.

Do

  • Use determinate mode whenever a real percentage exists; it communicates remaining time.
  • Align tone with the state (success when done, danger if it fails).
  • Put a ring where it has room to be read. A ring's own size is its whole geometry, so size moves the diameter and the band together, and there is nothing else to tune.
  • Pass a label: it is drawn beside the meter and is its accessible name. Without one the bar is announced as the generic "Progress", which tells a screen-reader user nothing about which of the page's bars it is.

Don't

  • Don't use indeterminate for processes you do know: it degrades visibility (H1).
  • Don't pass markup as label. The label is a plain string, precisely so the accessible name is the same words the sighted reader sees.
  • Don't replace a result ArenaToast with the bar; the bar reports progress, the ArenaToast reports the outcome.
  • Don't expect the bar to narrate every step. The bar carries aria-live="polite", because role="progressbar" has no implicit live region. The percentage is repeated inside that region as visually-hidden text, so the announcement is an ordinary content change rather than an attribute-only one. A bar that ticks continuously is chatty by construction. Announce a milestone that matters with an ArenaToast.
  • Don't project a control into a bar's middle: a bar has no middle and draws nothing for it.
  • Don't reach for a ring to save room. A ring at sm is smaller than a bar is long, and harder to read. The figure inside it is the point, because a meter nobody can read a number off is a decoration.
  • Don't read showPercentage={false} as silence. The member drops the number beside the label. The region keeps its own copy, because hiding a number visually is a layout choice rather than a reason to stop reporting progress.

The rules of the language hold in the code you write from this page. An Arena component is not a styling surface, so put no className of your own on it. Read every value through its token, never a raw colour and never a bare 16px. Never wrap it in your router's own link. arena-to-prod --audit reports these three in your sources. The rest are in ../../../../../skills/design/SKILL.md, which marks the ones it reports.