DEV Community

loading...
Cover image for 5 star rating system - ACTUALLY accessible, no JS, no WAI-ARIA and Semantic HTML! ⭐⭐⭐⭐⭐ [Easily converted to any framework too!]

5 star rating system - ACTUALLY accessible, no JS, no WAI-ARIA and Semantic HTML! ⭐⭐⭐⭐⭐ [Easily converted to any framework too!]

InHuOfficial
Specialising in accessibility and website load speed / performance. If you have a question about [accessibility] or [page-speed-insights] ask away and I will help any way I can!
・7 min read

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> 
Enter fullscreen mode Exit fullscreen mode

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;
   });
});
Enter fullscreen mode Exit fullscreen mode

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:

Click to Tweet: "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. You should check it out!"

Discussion (34)

Collapse
link2twenty profile image
Andrew Bone

Are... Are you using SVGs?! I'm telling @afif ...

Collapse
afif profile image
Temani Afif

He made the effort to integrate it inside the CSS so I will forgive him this time ... but the real issue is the ton of HTML used here ... more than what I used for my loaders 😩

Collapse
inhuofficial profile image
InHuOfficial Author

Throw accessibility out the window and I am sure you could do a single div star rating system. I couldn't but I am sure you could 😋

Thread Thread
afif profile image
Temani Afif

I already did it here: dev.to/afif/another-100-css-loader... I simply need to add the inputs and it will be interactive. Maybe I will write a post about 🤔

Thread Thread
inhuofficial profile image
InHuOfficial Author • Edited

For the love of God...noooooo, I promise I will be good and not use SVG in my next post!

If you do it people won't read any warnings you put on that it is just for fun and just copy paste it. Then I will have to republish this post every week to undo the damage (especially as lots more people read your stuff than mine, so I would need to work twice as hard 😋)!

I mean it is that bad that somebody literally just posted an article with a star rating system using <li> that isn't accessible (not even joking!) so I have an uphill battle as it is 😋🤣

(only kidding BTW, go for it if you have the time as I may be able to simplify my CSS from the tricks you use!)

Thread Thread
lapstjup profile image
Lakshya Thakur • Edited

Hey I am that person who posted about <li> star rating system and yes its fully JS based and is a really amateur implementation with regards to css and accessibility. I will surely re-read this blog again and again to understand things in better depth and maybe go ahead with building an accessible component. I actually wanted someone to comment about how my implementation lacks accessibility so I could get tips. I am glad you made this blog :D.

Thread Thread
inhuofficial profile image
InHuOfficial Author • Edited

Wasn’t meant to be a dig by the way (well not a mean dig, just a playful one!😜), Temani and I have good banter that is all and your timing was just too good releasing that post!

If you want I can have a look at your post and offer some suggestions on how you could make it accessible and keep you current design (or close to it).

Or better yet why not take the principles from this design and Use the revealing-module-pattern to turn into a component, more than happy to link to it / include your fiddle in this post if you do and happy to help if you need any guidance from an accessibility perspective! ❤️

Thread Thread
lapstjup profile image
Lakshya Thakur

I will try to understand the principles of your design first. And surely leave a comment for any help needed to understand accessibility. I am trying to learn it by reading blogs from web.dev and other online content. Let's see if I get a chance to make a revamped accessible one using same pattern :D.

Thread Thread
inhuofficial profile image
InHuOfficial Author • Edited

The key principle is using radio buttons with properly associated labels from an accessibility perspective (that are within a fieldset to group them). That covers 80% of the accessibility!

Once you have that as a base the only other thing that is a bit unusual is the way I hide the inputs (and the label text) so that is the key thing to investigate. If you search google for “screen reader only text” or “visually hidden text” the first articles should explain that technique in detail. this article explains the principles reasonably well to get you started

Once you have had a fiddle and poke around then let me know if anything still doesn’t make sense!

Collapse
inhuofficial profile image
InHuOfficial Author

Don't cause him undue harm, he might have missed that I use SVG and now he might notice and cry.

Then he might look at my CSS skills and cry some more.

Then in a fit of rage he will create the same item with one CSS selector and some CSS voodoo just to feel better. I don't want him to have to do that just to get through the day, I am sure he is busy! 😋🤣

Collapse
link2twenty profile image
Andrew Bone

Just noticed a problem on @afif 's version so came here to see if you have the same problem, and you do, but @madsstoumann doesn't 😲

Using the radio buttons the up and down arrow keys are inversed. Up makes the rating go down and vice versa.

Because Mads is using a range element it behaves as expected.

Collapse
inhuofficial profile image
InHuOfficial Author

It is expected behaviour though as you are cycling through a list of radio controls. If you inline radio inputs the behaviour is the same.

To be fair if user testing said it was an issue then our old friend JS can rescue us 😀

Collapse
mindplay profile image
Rasmus Schultz

Very cool!

Didn't work quite correctly with touch on a mobile phone though. Look here what happens if you tap and drag over the control, trying to scroll the page - it gets into some very weird states.

dev-to-uploads.s3.amazonaws.com/up...

Collapse
inhuofficial profile image
InHuOfficial Author

If you click elsewhere it would be fine, the red stars are for hover state and so you activated that.

I don’t think I ever saw someone drag on a star rating before but if you expect it to happen we can use media queries to fix it easily

Collapse
mindplay profile image
Rasmus Schultz

People will put their finger anywhere in the screen to scroll, because this does not normally translate to interactions with controls. I can put my finger on the submit-button in this comment form and scroll the page. This is fundamental to how touch interactions are expected to work, so it's going to look like a bug when it happens. Most internet devices are touch devices, so keeping things touch friendly is important when we design custom controls. 🙂

Collapse
mikemai2awesome profile image
Mike Mai

I love your intro. It is so true for a lot of devs to copy paste solutions without understanding them (everyone is guilty, my young self included). The problem is that the outdated posts have such high search talking, search engines really need to build something in their algorithm to counter that.

Collapse
inhuofficial profile image
InHuOfficial Author • Edited

The impossible task until true AI exists, find the best resource but then strip it from the internet when it is out of date while still having loads of shares!

I will keep carving away at the problem a few thousand views at a time until our AI overlords are born!

Collapse
magikmaker profile image
Bjørn

Interesting, I created something very similar about 3 years ago:
bjorn.wikkeling.com/199/pure-css-s...

It also only uses HTML and CSS for the stars, no SVG but just plain and simple UTF-8 stars. JS is only used for posting the values to an API.

Collapse
inhuofficial profile image
InHuOfficial Author

There is a star rating war on and this is useful information.

I like the simplicity so I am going to see if I can take some of this idea and use it! Nicely done! ❤

Collapse
mvoloskov profile image
Miloslav 🏳️‍🌈 🦋 Voloskov

Very good, but my screenreader just tells me “3 of 5”, “4 of 5” and so on. Wouldn't it be nicer to make it tell “Rating: 4 out of 5”? It should be easily achievable.

Nice work though! If it can be done without ARIA, it should, because otherwise my colleagues tend to make two conceptually different versions: one for so-called “normal” users and another for “not normal” ones 🤦 it's just like all those “mobile versions” over a proper responsive design. Core principle of a11y goes out the window.

Collapse
inhuofficial profile image
InHuOfficial Author

It would be expected that when you enter the control it is announced as rating and then just the numbers as you change.

You can change it to read as you want by changing the labels to include the text or by using `aria-label=“IDofLegend IDofLabel” and giving them IDs...but I wouldn’t do that unless user testing said it was an issue (and as it is currently expected behaviour I wouldn’t suggest it).

Screen readers can reannounce the current control if needed.

Collapse
afif profile image
Temani Afif • Edited

#webdev would be better than #codepen (there is no codepen in your post)

Collapse
inhuofficial profile image
InHuOfficial Author • Edited

done

Collapse
blackjyn profile image
ZVHR El Ekhsaan

Nice.
In today's web development "DIET" is the whole key

Collapse
inhuofficial profile image
InHuOfficial Author

Not familiar with the acronym “DIET”, or did I miss a joke...either way can you explain and educate me 😜🤣

Collapse
blackjyn profile image
ZVHR El Ekhsaan

It is a satire.
many developers are using complicated-big sized library/frameworks for things that native HTML/CSS/JS solutions are available.

Collapse
barelyhuman profile image
Reaper

That’s nice!

Collapse
inhuofficial profile image
InHuOfficial Author

Thanks Reaper, (great name by the way), I hope you find a use for it in the future!

Collapse
barelyhuman profile image
Reaper

I’ll use it for sure

Collapse
zaxwebs profile image
Zack Webster

This is pretty neat.

Collapse
inhuofficial profile image
InHuOfficial Author

Thanks Zack, it was the best approach I could come up with, that doesn't mean there isn't a cleaner way to achieve the same though 😋

Collapse
inhuofficial profile image
InHuOfficial Author

If there is anything I haven't explained well then please let me know! I hope some of you get to use this in your own projects (and if you do let me know in the comments!)

Collapse
vikkrantxx7 profile image
Vikrant Sharma

Nice article.. for the last point though you could have used event delegation 🤔.

Collapse
inhuofficial profile image
InHuOfficial Author

There are a hundred ways to tap into it, essentially I was just showing that all it is is a radio group and so you can attach it in any way to your application simply by accessing the :checked value on the group.

Same principle applies for displaying them, you can just set the checked value with JS.

I perhaps should have come up with a better JS example, it was just something I threw in after I wrote the article as I know some people get put off when they see a CSS and HTML only component thinking they can't use it with a framework / access the value with JS on the front end.