Published · Updated · v0.6.5 · MIT License · Source: progress.md
Progress
Circular indeterminate
Progress is the native progress element decorated with the Material track and its indicator from the --ui-progress-* tokens. The real element stays in the document, so its value semantics, aria-valuenow/aria-valuetext exposure, and determinate/indeterminate behavior work unchanged. Use it when an operation is in flight: determinate when the task has a measurable duration, indeterminate when only activity is known.
Guidance
When to use
Use progress when an operation is in flight. Use determinate when the task has a measurable duration, indeterminate when only activity is known. Use the circular variant for compact surfaces where a full-width track does not fit — a card action, an inline refresh.
When not to use
Do not use progress for static or instant feedback — a Toast or an inline alert communicates results better. If the task can be cancelled or paused, pair progress with an Icon button.
Usability
- Determinate progress carries a
value/maxpair; indeterminate is aprogresswithoutvalue. - Keep the native element in the document so its value semantics and
aria-valuenowexposure work unchanged. - The browser paints the indeterminate animation; Gelium only re-skins the track.
- For the circular variant, put the ring inside a
role="status"wrapper. Name the wrapper with anaria-labelor a linked visible label; the ring staysaria-hidden. - Under
prefers-reduced-motionthe ring stops as a static arc. The status region still announces, so the signal never depends on motion.
Accessibility
- Keep the native element: the
progresselement carries its name, value, and min/max semantics at no cost. - Always pair the element with an
aria-label(or a linked visible label) that says what is progressing. - Never rely on color alone: the indicator length carries the value, and for indeterminate the native motion is the signal.
- The circular variant needs an explicit live region.
role="status"announces the operation without JavaScript; the decorative ring staysaria-hidden. - In forced-colors mode the track repaints as
Canvasand the indicator asCanvasText. The circular ring repaints asCanvasTextwith its active arc inHighlight— progress survives without color.
See Choose the right control for the cross-component decision.
Specimen
The native determinate and indeterminate bars render live on this page. The circular variant is a decorative ui-progress-circle ring inside a role="status" wrapper. Its live specimen renders below.
Anatomy
The component is a single decorated progress element inside a .ui-progress wrapper. The circular variant is the same wrapper with the --circular modifier, holding an aria-hidden ring and a role="status" region.
<div class="ui-progress">
<progress id="upload" max="100" value="65" aria-label="Upload progress">65%</progress>
</div>
<div class="ui-progress ui-progress--circular" role="status" aria-label="Loading search results">
<span class="ui-progress-circle" aria-hidden="true"></span>
</div>
progress— the native element,appearance: noneonly for styling. The browser still reflects itsvalue/maxand toggles the indeterminate state.- Track — the element's track paints
--ui-progress-trackwith the--ui-progress-track-heightand--ui-progress-radiusshape. - Indicator — WebKit draws the value through
::-webkit-progress-value, Firefox through::-moz-progress-bar, both painted--ui-progress-indicator. ui-progress--circular— the circular indeterminate modifier on the wrapper. It is an inline-flex box sized by--ui-progress-circular-size, withrole="status"naming the operation.ui-progress-circle— the ring: a bordered circle in--ui-progress-trackwhose--ui-progress-indicatorarc sweeps on the sharedui-spinkeyframes. Decorative (aria-hidden), so the accessible name always lives on the wrapper.
States
Progress covers determinate (a value/max pair) and indeterminate (a progress without value). The browser exposes real progress to assistive tech and the platform paints the indeterminate animation. Gelium only re-skins the track so the motion stays the native one. The circular variant is indeterminate by design — a role="status" region around a decorative ring, sized for compact surfaces.
Design tokens
All progress paints are token-driven so the component survives light and dark schemes. The circular variant reads two scoped tokens on the wrapper (no theme tokens needed).
| Token | Meaning |
|---|---|
--ui-progress-track-height |
Track thickness (4px) |
--ui-progress-radius |
Track corner radius (--ui-radius-full) |
--ui-progress-track |
Track paint (surface-container-highest) |
--ui-progress-indicator |
Indicator paint (primary) |
--ui-progress-circular-size |
Ring diameter (scoped default --ui-size-item) |
--ui-progress-circular-stroke |
Ring stroke thickness (scoped default 4px) |
Accessibility
- Keep the native element: the
progresselement carries its name, value, and min/max semantics at no cost, and indeterminate state is announced correctly. - Always pair the element with an
aria-label(or a linked visible label) that says what is progressing — matching what is on screen. For the circular variant the label lives on therole="status"wrapper. - Never rely on color alone: the indicator length carries the value, and for indeterminate the native motion is the signal.
- Under reduced motion the ring stops as a static arc. The status region continues to announce the operation.
- In forced-colors mode the track repaints as
Canvasand the indicator asCanvasText. The circular ring repaints asCanvasTextwith its active arc inHighlight, so progress survives without color.