Progress ring
A circular counterpart to the linear progress bar.
There is no native circular-progress element, so this is a role="progressbar"
<div> drawn purely in CSS: a conic-gradient sweep (the value) with the centre
punched out by a radial mask, leaving a ring — with the numeric label in the hole.
No JavaScript: set the --re-progress-value custom property (0–100).
Values
Section titled “Values”<div class="row">
<div
class="re-progress-ring"
role="progressbar"
aria-label="Progress"
aria-valuenow="25"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="25%"
style="--re-progress-value: 25"
>
<span class="re-progress-ring__label" aria-hidden="true">25%</span>
</div>
<div
class="re-progress-ring"
role="progressbar"
aria-label="Progress"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="50%"
style="--re-progress-value: 50"
>
<span class="re-progress-ring__label" aria-hidden="true">50%</span>
</div>
<div
class="re-progress-ring"
role="progressbar"
aria-label="Progress"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="75%"
style="--re-progress-value: 75"
>
<span class="re-progress-ring__label" aria-hidden="true">75%</span>
</div>
<div
class="re-progress-ring"
role="progressbar"
aria-label="Progress"
aria-valuenow="100"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="100%"
style="--re-progress-value: 100"
>
<span class="re-progress-ring__label" aria-hidden="true">100%</span>
</div>
</div> Keep three things in sync: --re-progress-value (draws the arc), the visible label
text, and aria-valuenow / aria-valuetext (the value as it reaches assistive
tech). If JavaScript drives the ring, it writes all three.
<div class="row">
<div
class="re-progress-ring"
data-size="sm"
role="progressbar"
aria-label="Progress"
aria-valuenow="60"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="60%"
style="--re-progress-value: 60"
>
<span class="re-progress-ring__label" aria-hidden="true">60%</span>
</div>
<div
class="re-progress-ring"
role="progressbar"
aria-label="Progress"
aria-valuenow="60"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="60%"
style="--re-progress-value: 60"
>
<span class="re-progress-ring__label" aria-hidden="true">60%</span>
</div>
<div
class="re-progress-ring"
data-size="lg"
role="progressbar"
aria-label="Progress"
aria-valuenow="60"
aria-valuemin="0"
aria-valuemax="100"
aria-valuetext="60%"
style="--re-progress-value: 60"
>
<span class="re-progress-ring__label" aria-hidden="true">60%</span>
</div>
</div> Indeterminate
Section titled “Indeterminate”A spinning arc for work of unknown duration. Omit aria-valuenow and give the ring
an aria-label instead — there is no value to report, only a name. The spin
respects prefers-reduced-motion.
<div
class="re-progress-ring"
role="progressbar"
data-indeterminate
aria-label="Loading"
></div> Accessibility
Section titled “Accessibility”- Role & name — the ring is a
role="progressbar", which must have an accessible name (aria-label, oraria-labelledbypointing at nearby text) saying what is progressing. The%is the value, not the name, so it can’t stand in for it — a nameless progressbar is a WCAG 1.1.1 failure. - Value —
aria-valuenow/aria-valuemin/aria-valuemaxcarry the value;aria-valuetextgives it a human form (“70%”). The visible%label isaria-hidden(the value already reaches AT through the ARIA attributes) but stays in the DOM so it survives forced colors. - Forced colors (Windows High Contrast) — a
conic-gradientis a background image, which the UA drops, so the arc disappears. The fallback keeps the numeric%on screen and draws a neutralCanvasTexttrack ring, so the control still reads as a ring and the value is still visible. (Rendering the filled level under HCM would need an SVG stroke; that’s intentionally out of scope here.) - Motion — the indeterminate spin is paused under
prefers-reduced-motion: reduce.
See the accessibility guide for the project-wide approach.