Making Star Ratings and Product Reviews Accessible
AI generated
A11Y
WCAG
Accessibility · Reviews · Rating
Accessible Star Ratings
Announcing star ratings as text, review forms with radio buttons, and accessible filters

A star rating made of five filled and empty icons is instantly understandable for sighted users, but without extra markup it is either completely silent for screen reader users or a confusing sequence of five separate graphics with no recognizable overall value. Consistently making the rating's text alternative, the review form, and the sort and filter options for reviews accessible turns a purely visual trust signal into information every user can actually retrieve.

10 min read Star rating as text Review form without plain star clicks

1. Why a star rating without text is useless for screen readers

A typical star rating consists of five SVG or icon font elements, some rendered filled and some empty, to visualize for example a rating of 4 out of 5 stars. Without accompanying text, a screen reader sees at best five meaningless graphics in a row, at worst the icons are ignored entirely and the whole rating average stays invisible to non sighted users.

This problem shows up in three distinct places in a store, each needing its own solution: the aggregated star display in the product header, the input form for a new review, and the sort and filter functions inside the review list. All three places use the same visual star metaphor, but need different ARIA markup because they represent different interactions.

2. Announcing visual star ratings to screen readers as plain text

The most robust solution for a pure display rating, for example in the product header or a product listing, is to hide the entire star container from screen readers with aria-hidden="true" and instead insert an invisible but readable text like "Rating: 4 out of 5 stars" before or after it. This text should always be phrased as a complete sentence, not just a number, so its meaning is clear even without visual context.

Alternatively, the text can be set directly as an aria-label on the star container, keeping the individual star icons marked with aria-hidden="true". Both techniques lead to the same result, what matters is that both are never used at the same time, since aria-label and an additional visible alternative text could otherwise double up or contradict each other.


<div class="star-rating" aria-label="Rating: 4 out of 5 stars">
  <svg aria-hidden="true" class="star star--filled"></svg>
  <svg aria-hidden="true" class="star star--filled"></svg>
  <svg aria-hidden="true" class="star star--filled"></svg>
  <svg aria-hidden="true" class="star star--filled"></svg>
  <svg aria-hidden="true" class="star star--empty"></svg>
</div>

3. Precisely phrasing half stars and decimal values

Many stores display average ratings with one decimal place, for example 4.3 out of 5 stars, visualized by a partially filled star. The text alternative should not simply round up or down, it should reflect the actual average value, for example "Average rating: 4.3 out of 5 stars, based on 128 reviews". The number of underlying reviews belongs in the same announcement, since it decisively affects how meaningful the rating is.

For a half star rendered only half filled, the same rule applies: the text alternative reflects the precise numeric value, not a rough description like "four to five stars". Screen reader users should get the same precision sighted users get by visually reading a half filled star correctly.

4. Accessible review form: radio buttons instead of plain star clicks

When creating a new review, picking a star count must never work exclusively through clicks on non focusable icon elements, since without native form semantics they are neither reachable by keyboard nor understandable to screen readers. The accessible foundation is a group of five input type="radio" elements inside a fieldset with a legend, with every radio button representing one star count.

The visual star design stays fully intact, since the radio buttons are hidden with an sr-only technique, just like in the payment method selector, and connected to the visible star icon via a label element. The decisive difference from a purely decorative click handler is that keyboard users can navigate through the five star values with the arrow keys, and the screen reader reads out the label like "5 stars, excellent" for every value.


<fieldset class="rating-input">
  <legend>Your rating</legend>

  <label class="rating-input__star">
    <input type="radio" name="rating" value="5" required>
    <span class="sr-only">5 stars, excellent</span>
    <svg aria-hidden="true"><!-- star icon --></svg>
  </label>
  <label class="rating-input__star">
    <input type="radio" name="rating" value="4">
    <span class="sr-only">4 stars, good</span>
    <svg aria-hidden="true"><!-- star icon --></svg>
  </label>
  <!-- remaining values 3, 2, 1 follow the same pattern -->
</fieldset>

5. Keyboard operability and hover preview behavior of the star input

A common detail in star inputs is a hover preview, where moving the mouse over the stars temporarily fills more or fewer of them before the click sets the final value. This preview must happen purely visually through CSS, for example with the selector combination :hover ~ label, and must never change the actually selected aria-checked state, otherwise moving the mouse would keep triggering new, confusing announcements.

The hover preview does not apply to keyboard users, since focusing a radio button sets the selected value directly. It matters that the focused radio button gets a visible focus ring that is recognizable independent of the star fill state, so it stays clear which of the five values could currently be selected via keyboard.

6. Making sort and filter options for reviews accessible

Filters like "show only 5-star reviews" or a sort order like "newest first" should be implemented as a native select element or a group of buttons with aria-pressed, never as a plain clickable area with no form or button semantics. After every filter change, an aria-live="polite" region should announce the new result count, for example "32 reviews found with 5 stars", so screen reader users can immediately grasp the effect of the filter.

If the review list gets reloaded via JavaScript when filtering, without a full page reload, focus must be actively preserved, for example on the filter control itself, instead of disappearing uncontrolled. Completely rebuilding the list in the DOM without an accompanying announcement of the new result count is one of the most common reasons screen reader users simply do not notice a filter change.


<div id="review-filter-status" class="sr-only" aria-live="polite"></div>

<script>
function applyReviewFilter(count) {
  document.getElementById('review-filter-status').textContent =
    count + ' reviews found.';
}
</script>

7. Aggregated rating in the product header: combining schema.org and a jump link

The aggregated star display in the product header usually also carries the structured data markup for AggregateRating, which matters for search engines. This structured data format does not replace a visible, screen reader accessible text alternative in the rendered HTML, both layers must exist independently of each other, since search engines and screen readers evaluate different data sources.

In addition, the aggregated rating in the product header should function as a link to the review section further down the page, for example with the link text "View 128 reviews", so keyboard users can jump directly to the details instead of laboriously navigating through the entire product description.

8. Helpful marking for individual reviews as a toggle with a counter

The question "Was this review helpful?" with an associated counter follows the same toggle button pattern as a wishlist button, but with one added twist: the click changes not only its own button state via aria-pressed, but also a neighboring number. This numeric change must also be announced, for example through the same aria-live region already used for the filter change.

A common mistake is updating the counter directly in the visible button text without the screen reader picking up the change, since plain text changes inside an already focused but not live announced element are usually not read out automatically. An additional live region reliably solves this problem.

9. Implementation in Magento and Hyvä: rating and filter components with Alpine.js

In Hyvä, both the star input in the review form and the filter and sort logic can be encapsulated as standalone Alpine.js components, each using a shared, central live region in the review section for their status messages. That keeps the announcement logic bundled in one place instead of being duplicated separately in every component.

As with all inline script blocks, every status announcement logic must be registered via $hyvaCsp->registerInlineScript() in the corresponding phtml template, so the Content Security Policy does not block execution.


document.addEventListener('alpine:init', () => {
  Alpine.data('reviewFilter', () => ({
    resultCount: 0,
    apply(count) {
      this.resultCount = count;
      this.$dispatch('review-status', { message: count + ' reviews found.' });
    },
  }));
});
Rating context Technique Purpose Common mistake
Display rating in product header aria-label or aria-hidden plus text Announcing stars as plain text Stars with no text alternative at all
Half stars / decimal values Precise numeric value in the text Preserving accuracy for all users Rough rounding without a review count
Review form Radio buttons in fieldset/legend Keyboard and screen reader operability Only click handlers on icon elements
Filter and sorting select or buttons with aria-pressed plus live region Announcing result count after a change List changes silently
Helpful counter Toggle with aria-pressed and a live region for the number Announcing the counter change too Only visible text with no live announcement

Mironsoft

WCAG audits, accessible Magento shops, and training

Not sure whether the shop is actually accessible?

We audit existing Magento shops against WCAG 2.2, fix concrete barriers in the Hyvä frontend, and train teams so accessibility stays anchored in the development process for good.

WCAG Audit

Systematically review the shop against WCAG 2.2 AA, with a prioritized issue list.

Fixing Barriers

Concrete implementation: keyboard operability, screen reader support, contrast, forms.

Team Training

Raise developer and editor awareness for accessible implementation day to day.

10. Summary

Accessible Star Ratings: The Essentials

Core idea

Every visual star rating needs a textual equivalent phrased as a complete sentence, not just a number.

Review form

Native radio buttons inside a fieldset replace plain icon clicks and supply keyboard operability automatically.

Filter and sorting

A live region must actively announce the new result count after every filter change.

Structured data

AggregateRating markup does not replace a visible, screen reader accessible text alternative in the rendered HTML.

11. FAQ: Accessible Star Ratings: The Essentials

1How do I correctly announce a visual star rating to screen readers?
Via aria-label on the container or an sr-only text as a complete sentence like Rating: 4 out of 5 stars, while the individual icons are hidden with aria-hidden.
2Should a decimal value like 4.3 stars be rounded when announced?
No, the precise value should be preserved, ideally together with the number of underlying reviews for more context.
3Why is a click handler on star icons not enough for a review form?
Without native form semantics, plain icon elements are neither keyboard focusable nor recognizable to screen readers as selectable values.
4How does the visual star design stay intact with radio buttons?
The radio buttons are hidden with an sr-only technique and connected to the visible star icon via a label element, similar to the payment method pattern.
5Can a hover preview change the actually selected value?
No, the preview should happen purely visually through CSS and must not touch the form's aria-checked state.
6How should a filter change on reviews be announced?
Via an aria-live=polite region that outputs the new result count as text after every change, for example 32 reviews found.
7Does AggregateRating markup replace the visible text alternative for stars?
No, structured data matters for search engines, an independent text alternative visible in the rendered HTML remains mandatory for screen readers.
8Should the star display in the product header be linked?
Yes, a link with text like View 128 reviews to the review section lets keyboard users jump directly there without laborious navigation.
9How does the helpful marking work accessibly?
As a toggle button with aria-pressed, whose accompanying counter change is additionally announced via an aria-live region.
10Is a plain text change in the button enough for the helpful counter without a live region?
No, plain text changes in an already focused element are usually not read out automatically without a live region.