Loyalty Points and Rewards Progress Bars: Getting the ARIA Meter Pattern Right
Quick answer: a loyalty or rewards points bar (“120 of 500 points to Gold status”) should use
role="meter" (or the native HTML <meter> element), not role="progressbar". The WAI-ARIA APG
Meter pattern states this directly: “The meter
should not be used to indicate progress, such as loading or percent completion of a task. To
communicate progress, use the progressbar role instead.” A points balance is a static measurement
within a known range — the same category as a fuel gauge or a battery indicator — not a task that’s
actively in progress. Most e-commerce builds get both parts wrong: no ARIA role at all, and if one
is added, it’s usually the wrong one.
This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA is a technical-practice standard, not a legal determination — consult qualified counsel for legal risk questions.
Why this control fails so consistently
A rewards progress bar is almost always built the same way: an outer <div> sets a fixed width and
background color for the track, and an inner <div> gets its width set inline or via a CSS
variable to some percentage — 24% for “120 of 500 points,” say. Visually this works fine. To a
screen reader, both divs are just generic containers. There’s no accessible name (“Progress toward
Gold status”), no exposed current value, and no minimum or maximum — nothing that turns “a
colored rectangle 24% as wide as its parent” into “120 of 500 points.”
This is a narrower, more specific problem than the general div-based-widget failures covered in our wishlist and save-for-later buttons guide or color and size variant swatches guide — those are toggle controls; a rewards bar is a range-value display, a different widget category with its own dedicated ARIA role and its own easy-to-get-wrong mistake once someone does reach for ARIA.
The wrong fix: role="progressbar"
Once a team knows a plain div needs some ARIA role, progressbar is the intuitive pick — the
word “progress” is right there in “progress toward your next reward.” But the WAI-ARIA 1.2
specification’s progressbar role defines it
specifically as “an element that displays the progress status for tasks that take a long time,”
where “the application is making progress toward completing the requested action.” That describes
a file upload, a multi-step checkout submission, or a page still loading data — an active process
with a start and an end. A points balance isn’t a process. It doesn’t start, run, and finish; it’s
a number that sits at whatever value it currently holds until the next purchase changes it.
The spec’s meter role is explicit about the distinction from the other side too: it’s “a scalar measurement within a known range, or a fractional value,” and its own guidance reads: “Authors SHOULD NOT use the meter role to indicate progress; the progressbar role exists to address that need.” The APG’s worked examples for meter are a device’s battery percentage and a car’s fuel level — both static readings against a fixed range, exactly the shape of a points balance.
The right fix: role="meter", or the native <meter> element
The simplest correct build skips ARIA entirely and uses the native HTML <meter>
element, which
carries an implicit ARIA role of meter automatically and needs no extra attributes to be
understood by assistive technology:
<label for="rewards">Progress to Gold status</label>
<meter id="rewards" min="0" max="500" value="120">120 of 500 points</meter>
Where a custom-styled bar is a hard requirement — most rewards-page designs are — the MDN ARIA meter role page gives a real worked example of the div-based equivalent, built the same way its own CPU-usage example is:
<div
role="meter"
aria-valuenow="120"
aria-valuemin="0"
aria-valuemax="500"
aria-labelledby="rewards-label"
>
<div style="width: 24%"><!-- visual fill --></div>
</div>
<span id="rewards-label">Progress to Gold status</span>
aria-valuenow, aria-valuemin, and aria-valuemax are what actually let a screen reader
announce something meaningful. Per the WAI-ARIA APG Meter
pattern: “Assistive technologies often present
aria-valuenow as a percentage. If conveying the value of the meter only in terms of a percentage
would not be user friendly, the aria-valuetext property is set to a string that makes the meter
value understandable” — its own example is a battery announced as aria-valuetext="50% (6 hours) remaining" rather than a bare “50%.” A points meter benefits the same way: aria-valuetext="120 of 500 points" is a far more useful announcement than a screen reader converting 24% on its own.
The trap once role="meter" is added: visible text still isn’t exposed
A common half-fix: someone adds role="meter" to the outer div, sees it validate, and assumes the
visible “120 of 500 points” text inside the bar is now accessible because it’s sitting right there
in the markup. It isn’t. MDN’s meter role documentation spells out why: “there are some types of
user interface components that, when represented in a platform accessibility API, can only contain
text… browsers automatically apply role presentation to all descendant elements of any meter
element.” Concretely, <div role="meter">120 of 500 points</div> gets treated the same as
<div role="meter"><span role="presentation">120 of 500 points</span></div> — the descendant text
is stripped from how assistive technology sees it. The value has to come from aria-valuenow (plus
aria-valuetext if a raw number or percentage wouldn’t be clear on its own), not from whatever
text happens to be visually nested inside the element.
Color-only fill (WCAG SC 1.4.1 Use of Color)
A second, independent failure shows up even after the role and value are fixed: many rewards bars
communicate tier proximity purely through fill color — green once you’re close to the next tier,
gray/amber further away — with no text stating the tier name or points-remaining anywhere near the
bar. WCAG SC 1.4.1 Use of Color
(Level A) states: “Color is not used as the only visual means of conveying information, indicating
an action, prompting a response, or distinguishing a visual element.” A sighted user with a color
vision deficiency may not reliably read the color-coded urgency signal even though they can see the
bar itself. The fix doesn’t require redesigning the bar — a visible text label (“340 points to
Gold”) next to or below the fill satisfies this for sighted users, while the aria-valuetext
covered above handles the same information for screen reader users.
What a scanner catches here, and what still needs a person
Automated tools have real, specific coverage in this exact area — and a real, specific blind spot.
axe-core ships dedicated aria-meter-name and aria-progressbar-name rules that flag either role
when it has no accessible name, plus a general aria-required-attr rule that flags a meter or
progressbar missing its required aria-valuenow. What none of those rules can catch: whether
progressbar was the wrong role to pick for a static value in the first place. A scanner reads
markup, not intent — a fully valid, fully-named role="progressbar" with a correct aria-valuenow
passes every automated check cleanly, while still describing a rewards balance as if it were an
active task in progress. Catching that requires someone who understands what the widget actually
represents, not just whether its ARIA attributes are well-formed.
FAQ
Is a rewards points bar ever correctly a progressbar?
Only if it’s genuinely representing an in-progress action with a start and end — for example, a
one-time animated bar shown while a points balance is being recalculated after a purchase posts.
The persistent “how close am I to my next tier” display itself should be meter.
Do I need both aria-valuenow and aria-valuetext?
aria-valuenow is required either way. Add aria-valuetext when the raw number or the percentage
a screen reader derives from it wouldn’t be clear on its own — “120 of 500 points” communicates
more than “24%.”
Does the native <meter> element support custom styling?
Yes, via vendor pseudo-elements and modern CSS (accent-color, and browser-specific
::-webkit-meter-* selectors per MDN’s own reference) — though a fully custom visual design is
often easier with the ARIA div-based pattern above, at the cost of having to wire up the
attributes by hand instead of getting them for free.
Get your interactive widgets checked by a person, not just a parser
Quietramp’s $890 audits include a manual pass through account-area widgets like loyalty and rewards displays — checking not just that an ARIA role is present and valid, but that it’s the correct role for what the widget represents, and that a real screen reader announces something a shopper can actually use. See a real sample report or check pricing — $99/month for ongoing re-checks after fixes ship.
This article is general accessibility information, not legal advice. Meeting WCAG 2.1 AA does not resolve ADA or any other legal compliance question, and nothing here should be read as a compliance guarantee. Consult qualified counsel for legal risk questions.
This article was drafted with AI assistance and reviewed by a person for accuracy before publication.