In this article I will explain how I built a star rating system that is ACTUALLY accessible, easy to style for your own needs and easy to integrate into any framework as it uses semantic HTML.
If you are busy just skip straight to the first example
Introduction
I have seen several star rating systems shared recently which are completely inaccessible to people using a screen reader (a device that allows websites to be read out loud to people or converted to braille) or people who rely on a keyboard (people with movement disorders / accuracy issues such as Parkinson's Disease, Cerebral Palsy etc.).
I get frustrated by this lack of thought from authors as they release code into the world that contributes to a lack of accessibility.
Then people just copy and paste example code with no thought (or they may be new to development and not know about accessibility yet) and accessibility issues perpetuate forever (hence why we still have people using <a href="#"
for buttons...an overhang from 13 years ago when we couldn't style <button>
s...that is how long it takes to kill a bad practice!)
Anyway, this isn't one of my angry rants, so here it is, my first contribution to trying to fix this problem, a truly accessible star rating system.
An accessible star rating system
Why is this better than the other examples you have seen before?
- It uses semantically correct elements -
radio
inputs as this is a choice between ratings. - By using semantically correct elements it has no need for WAI-ARIA which doesn't actually have as much support as people think.
- It can be made to work all the way back to IE9 with a couple of minor adjustments, which is important as there are still a lot of screen reader users (12.4%!) using IE9, 10 and 11.
- No JavaScript so it will be lightning fast even on cheap hardware.
- Works on any framework...you can simply just hook into the radio group value as if it was a standard radio group (because essentially it is)!
- You can style it pretty much however you want by simply changing the SVGs for checked, unchecked and hover states and the display size. I would encourage you to create some nicer SVGs as these are rough and ready for demo purposes!
- It will support as many stars as you want (albeit you have to adjust a couple of things and add a couple of rules to the CSS)
- You can safely copy paste the CSS and HTML and know that it is accessible!
Accessible star rating system Example
Try it with a keyboard, mouse, screen reader (if you know how to use one) etc. It should work flawlessly.
IE9, IE10 and IE11 compatibility
There are only two things that won't work in IE9, IE10 and IE11.
The first is using CSS variables - so simply swap those out for the actual values.
The second is focus-within
to put the focus around the box. Instead for IE we just put focus indicators around the <span>
that contains the stars.
The beauty of this second example is it lets you see how everything works if you use a keyboard to focus the item! (you can see how the labels are stacked on top of each other and different widths to achieve our star rating effect).
Accessible star rating system IE9+
An explanation of the logic.
<fieldset>
is a semantically correct way of grouping controls. As the radio "buttons" all relate to the same item (your star rating) this lets screen reader users know what they are answering.
We use <input type="radio">
as that is the most logical HTML form element. You should use radio
buttons whenever there are multiple choices but only one can be selected at a time.
To ensure the inputs have a label that is correctly associated I use for
on the label to point to the relevant input with that ID. This is important as screen reader users need a correctly associated label so they know what an input is for. Otherwise they just hear "input" - which is not very useful!
Just for reference, you can do the same (correctly associate a label with an input) using:
<label>
<input type="radio">
</label>
But apparently Dragon Naturally Speaking struggles with implicit labels so I went for maximum compatibility.
What is with the <span>
s inside the label though?
The <span>
is for screen reader users.
I hide the text visually using a class called visually hidden text.
This text is invisible on the screen but is still readable by screen reader users.
This way when they focus the star rating system and select an option they will hear "Your rating: 3", "Your rating: 4" etc. or similar.
Without this they would have the same issue of just hearing "input" as although I provided a label there would be no text within it.
You will notice I apply the same styles to the <input>
as well to make it invisible visually but still accessible for screen readers.
This is the biggest problem most star rating systems have, they hide the <input>
with display: none
.
This means you cannot focus it anymore with Tab and so it is completely unusable for people who only use a keyboard.
Colour is important too
Super quick one here - colour contrast is important.
A lot of star rating systems use yellow stars with no border. This provides terrible contrast with the background and can be an issue for people with low contrast perception.
As such I have a dark grey border around my stars so they stand out even for people with contrast perception impairments (or people trying to use the site in direct sunlight....I am sure you know how annoying that can be on low contrast sites!).
I also went an extra step of making the border on the stars different sizes depending on their current state. This allows for a visual difference that doesn't rely on colour at all! If you create your own SVGs I would encourage you to do something similar to provide visual distinction that doesn't rely on colour alone!
Simple to adapt to your own needs
If you want to use a different icon that is super simple.
You need three versions of your star as SVGs, a filled version, an unfilled version and a hovered version. Ideally they should be square to avoid having to alter the CSS.
Then just copy the SVG text into this converter press "convert" and then copy the result.
Paste the resulting CSS after "background-image: " into the variables --unchecked-image
(for no star), --checked-image
(for star selected) and --hovered-image
(for hover state).
You can also have a ten star system if you want, in this example I have changed the --max-stars
CSS variable to 10.
You can have between 2 and 10 stars by simply adding the right number of radio buttons and then changing the --max-stars
CSS variable to match.
And as a final note I have designed the CSS so it will not leak into your document (unless you happen to use the same CSS variable names!) so you should be able to just copy paste and go without any Cascade issues.
One last trick that was used to make this work
If you inspect the elements you will notice that I have cheated a little bit.
What I have done is stack the labels on top of each other but make each one slightly smaller (one stars width smaller) each time I stack the labels.
Then we just let the background (the star SVG) repeat and fill the label.
So for a 4 star rating system we have 4 labels. The one for 4 stars is 100% width, the one for 3 stars is 75% width. This means that when you hover or click on the fourth star it is on the bottom label (as the 3 star label doesn't cover it) and when you click on the third star it is on the label above it.
We repeat this process all the way to one star.
The only annoying thing is that to maintain a logical DOM order (so that pressing the down arrow increases the number of stars) we have to use the z-index
property to put the last option to the back (as naturally it would want to be at the front).
Mini bonus fiddle to help use with JS libraries
If you want to get the current value of the star rating system in JS it is super simple (you can use the selectors / principle to convert it to any JS framework you are using.)
var rating = 0;
document.querySelectorAll('.star-rating input[name="rating"]').forEach(function(radio){
radio.addEventListener('change', function(){
rating = document.querySelector('.star-rating input[name="rating"]:checked').value;
});
});
You can see that in action here:
Obviously the whole thing works without JS and you can just use it as part of a normal form POST / submit but I thought I would add that just to show how easy it is to convert!
Summing up
I think the above is about as simple as you can get for a rating system that is accessible and has very high browser coverage.
Now as I am preaching about accessibility if anyone does notice a mistake please do call me an idiot and point it out! Hopefully I haven't made a mistake somewhere 🤞.
With that being said, I am quite confident the above is truly accessible and I would be confident in saying that you can use it in your own projects.
Share this and spread the word please!
If enough people read this article that is one component on the web that hopefully will be accessible to everybody.
To make it easy you can just click the share button below: