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.
Table of Contents
- 1. Why a star rating without text is useless for screen readers
- 2. Announcing visual star ratings to screen readers as plain text
- 3. Precisely phrasing half stars and decimal values
- 4. Accessible review form: radio buttons instead of plain star clicks
- 5. Keyboard operability and hover preview behavior of the star input
- 6. Making sort and filter options for reviews accessible
- 7. Aggregated rating in the product header: combining schema.org and a jump link
- 8. Helpful marking for individual reviews as a toggle with a counter
- 9. Implementation in Magento and Hyvä: rating and filter components with Alpine.js
- 10. Summary
- 11. FAQ
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.