DEV Community 👩‍💻👨‍💻

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

Posted on

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

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!"

Top comments (38)

Collapse
 
anevins12 profile image
Andrew Nevins

I support the idea of the need for an accessible rating system, but I don't believe this is a final product. It's not "accessible" if it fails other user groups and it's worth running through the criteria on WCAG 2.1. For instance, the chosen ratings are only distinguishable through color (1.4.1) and there are no visible labels (3.3.2.)

Collapse
 
grahamthedev profile image
GrahamTheDev Author

I think it works and would pass WCAG AAA, but I am always open to a discussion on stuff!

I think you may have misunderstood "distinguishable by colour", an empty star with an outline vs a filled star with no outline vs a filled star with an outline is visually different even with Achromatopsia (complete colour blindness). It could be black and white and still perfectly obvious which stars are filled and which are empty due to side differences and fill differences. Where it would fail is if I used a red star initially and then turned them green when selected, then there is no visual difference, only a colour difference.

Also the stars have a visible label in that they are in a fieldset with a legend, which labels the controls purpose, and for knowing what is selected: visually the number of ratings is easily determined by the number of filled stars.

However you could address both quite easily by updating the legend with the current rating in brackets (Your rating (current: 0 out of 10): ) and provide additional instructions via aria-labelledby or aria-describedby if you believe that a star rating pattern is not a recognised and understood pattern in your Country or within a community (as that would be the biggest problem, unfamiliarity with a star rating system would make this hard to understand).

I would say this passes the 99% rule - 99% of people will both understand and be able to use the control as nothing is "perfect" when it comes to accessibility (for example this may not be the best design for eye-gaze technology if the stars are smaller).

I would pass it on a VPAT, but as always I am more than willing to make improvements if you have any suggestions, as I love when people blow my mind with something I hadn't considered or a novel idea! 💪❤

Collapse
 
anevins12 profile image
Andrew Nevins • Edited on

Good point on the border for 1.4.1, I hadn't noticed the subtle border change between chosen and other star shapes.

You are mistaken on 3.3.2 Labels or instructions when you mention programmatic structure, accessible names, or accessible descriptions. This criterion is not intended for screen readers.

Look man, I'm on your side, but it peeves me that you mentioned it is accessible and do not take feedback.

Edit: I would caution you about using the words that it works for the majority of users, as people with disabilities will always be a minority group.

Thread Thread
 
grahamthedev profile image
GrahamTheDev Author • Edited on

Yes you are correct, I had the wrong rule in mind, however the principle is the same.

If it was deemed that the stars themselves do not provide a clear enough indication of behaviour then a set of instructions which explains the usage of the control would be beneficial. These would be linked using aria, but I did indeed look at it from the wrong perspective, they need to be visible instructions.

The argument I was making (not well evidently) is that a star rating system is a widely used and understood UI pattern and the "labels" for each of the options are the stars themselves, so this is not a necessary step. However that would certainly be a great "belts and braces" accessibility improvement to add instructions for usage!

You could improve this further by adding a visible label under each star to denote the number, but then you have to consider whether that adds confusion when it shows all possible star rating numbers (as you are still relying on the stars themselves to convey the number selected), i.e. if we have 5 stars and 3 are selected, a visible label under each of the stars could cause confusion as it says "1 star, 2 star, 3 star, 4 star, 5 star".

As for taking feedback...I also said I am willing to listen and welcome any suggestions to improve it in the last paragraph, at the moment you are only giving me potential problems (which isn't useful feedback I can action as I do not agree (yet)...but I may be misunderstanding the point you are trying to make). Points that I would argue are covered. I will always improve my solution if there is a better alternative and really would like to hear what you think would make it better.

Perhaps the alternative is simply to not use a star rating system at all, but I am still quite confident in saying this is an accessible (as far as is possible with current technology) star rating widget...the argument for whether star rating widgets themselves are accessible is an interesting one though.

And the majority wording yet again I was very careful with - nothing is 100% accessible, so the 99% rule is the best we can do.

Finally I want to be clear, I am not ignoring you, neither am I not listening, I welcome feedback like this as it helps me learn and grow, I am just not understanding where you think this pattern can be improved. Please do continue to elaborate on where you think improvements can be made and I will action them once I understand the benefit / improvement!

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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev Author • Edited on

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
 
lakbychance profile image
Lakshya Thakur • Edited on

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
 
grahamthedev profile image
GrahamTheDev Author • Edited on

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
 
lakbychance 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
 
grahamthedev profile image
GrahamTheDev Author • Edited on

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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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
 
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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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
 
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
 
grahamthedev profile image
GrahamTheDev Author • Edited on

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
 
blackjyn profile image
ZVHR El Ekhsaan

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

Collapse
 
grahamthedev profile image
GrahamTheDev 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
 
afif profile image
Temani Afif • Edited on

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

Collapse
 
grahamthedev profile image
GrahamTheDev Author • Edited on

done

Collapse
 
barelyhuman profile image
Reaper

That’s nice!

Collapse
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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
 
grahamthedev profile image
GrahamTheDev 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.

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.