In a hurry? Skip to the demo page.
Many ecommerce sites, social networking services, and online communities include rating or assessment features. Soliciting people’s opinion has even become a business model; there are now sites dedicated to rating products, services, businesses, and more.
The most common interface used to display votes is the “star rating system,” in which a particular number of points (often expressed as stars) is assigned to an item by each reviewer. We find this model on many sites, from Amazon to Yelp.
As Figure A shows, both visual interfaces are similar, but what makes these two solutions interesting is their markup base. One relies on <map>, the other on <img>.
You might think that most rating systems would be based on some markup proven to be semantic and "operational" across many User Agents — that is, that rating systems would be based on a specific set of HTML elements and attributes to which one applies behavior and style via JS and CSS. That would make sense, but it is far from the truth. When it comes to markup, authors try just about everything:
<a>,<img>,<span>,<li>,<map>,<div>,<input>,Before presenting a few image-based techniques to mark up ratings, I think it is worth mentioning a basic and straightforward approach (from Microformats) that uses characters:
<abbr class="rating" title="3 stars">***</abbr>
Note: I use "*" rather than ★ (★) because screen-readers (at least JAWS and NVDA) seem to ignore html entities.
When it comes to display images, authors have many options.
Using a single image:
<img src="4stars.png" alt="4 out of five">





From the whatwg‘s working draft:
<img alt="4 out of 5" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="no-star.png">
img elements per rating diminishes the number of HTTP requests.Note that this is taken from a controversial working draft. In my opinion, this method is not acceptable because the alternate text does not describe the image accurately and succinctly. Besides, if the basis of this approach is that these images represent content, then why leave some of them with no alt text?
On Ajaxian, for example, the author is using alternate text with every single image, which makes a lot of sense if he considers that each one is content:
<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="-" src="star0.png"/>
<img [snip] alt="-" src="star0.png"/>
In any case, using as many images as there are stars versus using a single element (an img or something else) has the main advantage of facilitating voting mechanisms – where a user selects one of the stars to cast his vote. So we should keep this in mind…
The following technique is a adaptation of a strategy originally implemented by developers at Yahoo! Music:
<span class="rating r1 stars">1 of 5</span>
<span class="rating r2 stars">2 of 5</span>
<span class="rating r3 stars">3 of 5</span>
<span class="rating r4 stars">4 of 5</span>
<span class="rating r5 stars">5 of 5</span>
.stars {
background: transparent url(img/sprite.png) no-repeat;
}
.rating {
font-size: 0;
height: 19px;
overflow: hidden;
vertical-align: middle;
width: 96px;
display: block;
}
.r1 { background-position: -385px 0; }
.r2 { background-position: -288px 0; }
.r3 { background-position: -192px 0; }
.r4 { background-position: -96px 0; }
This approach is based on the TIP method, which uses a sprite image as an <img> element rather than a background image:
<span title="1 of 5" class="rating r1"><img width="0" height="1" src="sprite.gif" alt=""/>1 out of 5</span>
<span title="2 of 5" class="rating r2"><img width="0" height="1" src="sprite.gif" alt=""/>2 out of 5</span>
<span title="3 of 5" class="rating r3"><img width="0" height="1" src="sprite.gif" alt=""/>3 out of 5</span>
<span title="4 of 5" class="rating r4"><img width="0" height="1" src="sprite.gif" alt=""/>4 out of 5</span>
<span title="5 of 5" class="rating r5"><img width="0" height="1" src="sprite.gif" alt=""/>5 out of 5</span>
.rating {
position: relative;
height: 1.6em;
width: 8.1em;
overflow: hidden;
vertical-align: middle;
display: block;
}
.rating img {
position: absolute;
width: 40.5em;
height: 1.55em;
top: 0;
border: 1px solid #fff;
}
.r1 img { right: 0; }
.r2 img { left: -24.4em; }
.r3 img { left: -16.2em; }
.r4 img { left: -8.1em; }
img elements.It is worth noting that unlike other Image Replacement techniques, this method allows:
To cast votes, we need a low-level voting mechanism that allows simple user selection and submission. For this, we can rely on using a form with labels and controls:
<fieldset>
<legend>Rating</legend>
<label><input type="radio" name="movie" value="1_5">1/5</label>
<label><input type="radio" name="movie" value="2_5">2/5</label>
<label><input type="radio" name="movie" value="3_5">3/5</label>
<label><input type="radio" name="movie" value="4_5">4/5</label>
<label><input type="radio" name="movie" value="5_5">5/5</label>
</fieldset>
For better legibility, we add <br> and whitespace.
<fieldset>
<legend>Rating</legend>
<label><input type="radio" name="movie" value="1_5"> 1/5</label><br>
<label><input type="radio" name="movie" value="2_5"> 2/5</label><br>
<label><input type="radio" name="movie" value="3_5"> 3/5</label><br>
<label><input type="radio" name="movie" value="4_5"> 4/5</label><br>
<label><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset>
For this solution, we are using a smaller sprite than the one in the example above. It is now composed of two single stars (“on” and “off”).
We place img elements inside the labels. We assume they will have no value without CSS support, thus we "hide" them by setting specific dimensions via their width and height attributes. Note that using 0 with both attributes would show a broken image in some UAs.
<form ...>
<fieldset>
<legend>Rating</legend>
<label class="one" title="1 out of 5"><input name="LandOf" value="1" checked="checked" type="radio"> 1/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
<label class="two" title="2 out of 5"><input name="LandOf" value="2" type="radio"> 2/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
<label class="three" title="3 out of 5"><input name="LandOf" value="3" type="radio"> 3/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
<label class="four" title="4 out of 5"><input name="LandOf" value="4" type="radio"> 4/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
<label class="five" title="5 out of 5"><input name="LandOf" value="5" type="radio"> 5/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
</fieldset>
</form>
Note that with the above markup, we can expect (in most browsers) field selection via label selection.
Unfortunately, as is, this markup creates issues in at least two screen-readers: JAWS and NVDA (see test case for these bugs). The problem is related to the use of a title attribute and an empty string for alternate text.
The workaround to not confuse screen-reader users is to use "stars" as alternate text (alt) and use JavaScript to insert title on mouseover.
<fieldset>
<legend>Rating</legend>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5"> 1/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5"> 2/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5"> 3/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5"> 4/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset>
We use em to allow the image to grow or shrink depending on font-size.
Unchanged
img {
width:2.8em;
height:1.4em;
}
As you can see already, clicking on an image selects the corresponding radio button. There is no need for scripting as implicit labeling produces this behavior (except in IE).
Styling the label with position:relative and the image with position:absolute with top/left values is enough to hide input and text inside the labels.
Unchanged
label {
position:relative;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
}
We style the label so its dimensions match the height and width of a single star.
Unchanged
label {
position:relative;
height:1.4em;
width:1.4em;
overflow:hidden;
display:block;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
}
We remove the brs and we float the labels.
Unchanged
br {
display:none;
}
label {
position:relative;
height:1.4em;
width:1.4em;
overflow:hidden;
display:block;
float:left;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
}
To set a "3 out of 5" rating, we apply the same class to the last two labels. This class will shift the position of the image inside the label.
<fieldset>
<legend>Rating</legend>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5"> 1/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5"> 2/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5"> 3/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5"> 4/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset>
br {
display:none;
}
label {
position:relative;
height:1.4em;
width:1.4em;
overflow:hidden;
float:left;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
}
.no_star img {
left:-1.4em;
}
It’s important to offer an alternative to the display of stars in case images are not available. This is because labels and radio buttons are styled to be on top of each other. A simple solution is to move input and text off-screen (i.e. using text-indent:-999em) and apply a background color to the labels.
No change
br {
display:none;
}
label {
position:relative;
height:1.4em;
width:1.4em;
overflow:hidden;
float:left;
background:teal;
margin-right:1px;
text-indent:-999em;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
}
.no_star {
background:#ccc;
}
.no_star img {
left:-1.4em;
}
Note:
text-indent also fixes a upwards jump of the image each time the controls get focus.:hover to create some rollover effect,Unchanged
br {
display:none;
}
label {
position:relative;
height:1.4em;
width:1.4em;
overflow:hidden;
float:left;
background:teal;
margin-right:1px;
text-indent:-999em;
}
input {
position:absolute;
left:-999em;
top:.5em;
}
img {
width:2.8em;
height:1.4em;
position:absolute;
top:0;
left:0;
cursor: pointer;
}
.no_star {
background:#ccc;
}
.no_star img {
left:-1.4em;
}
label:hover {
opacity:.5;
filter:alpha(opacity=50);
}
fieldset {
border:0;
}
legend {
text-indent:-999em;
}
Note: label:hover is ignored by IE6 and in Opera the background color bleeds through the images. In the demo page, instead of using opacity, I am using a different sprite that shows four states.
We can make the ratings "read-only" by adding disabled and checked attributes in the appropriate input fields.
<fieldset>
<legend>Rating</legend>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5" disabled> 1/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5" disabled> 2/5</label><br>
<label><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5" checked="checked"> 3/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5" disabled> 4/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif" width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5" disabled> 5/5</label>
</fieldset>
The rule using :hover has been removed
h4>Result
At this point, it is possible to cast votes without script support, but sighted users have no clue about their selection. So we use JavaScript to:
At the same time, we take advantage of using a script to insert title attributes that will create "tooltips" when users hover over the labels/stars.
Because of the lack of feedback regarding selection without JavaScript, we style labels and form controls only if there is script support. To do so we use JavaScript to set a flag on the html element and then we create a rule based on descendant selectors containing that hook. If the flag is missing, that rule does not apply and elements are not styled.
This is the demo page, the final product. To see how this solution behaves according to various settings, you may want to use your favorite developer tools to increase text-size, break image paths, disable JavaScript, turn CSS off, and more…
Coming up with a "acceptable" solution requires to identify users’ needs, User Agents’ peculiarities, User Agents’ settings and more – which means extensive testing.
In this process, users’ feedback is essential because following best practices is not always a sure thing. For example, as mentioned earlier, setting no value for the alt attribute of the images within the labels seem to be the safe thing to do, but it turns out that it creates issues with at least two screenreaders (see test case).
Also, feedback from assistive devices’ users allows to ignore some validation error messages – as the one that the Firefox Accessibility Toolbar reports (according to http://bestpractices.cita.uiuc.edu/html/nav/form/).
The goal here was not to fix everything, though. Being able to cast votes without a pointing device was one of my priorities, but improving the look and feel of the solution in Opera when images are disabled is not something I consider essential.
The most interesting part of this "journey" was to make the solution accessible to many users under various conditions, addressing issues such as:
It is also nice to know that this technique relies on img elements rather than background images, which allows the stars to:
All of this comes without sacrificing performance, as this solution relies on this single sprite: ![]()
I recently discovered the system Amazon has built for its voting page. It is quite interesting as they serve a different solution depending on script support. If there is script support, they use an image <map> (interesting approach), if there is no script support they use radio buttons. In both cases, the solution is accessible to keyboard users, and this helps to maximize access to a feature that is a core differentiator for the Amazon platform.
Note that they do not use JavaScript to replace the radio buttons with a image <map>; instead, they use noscript elements in which table markup contains radio buttons.
Special thanks to Victor Tsaran and Todd Kloots for their valuable feedback.
August 24, 2010 at 11:24 pm
[...] via Developing an Accessible Star Ratings Widget » Yahoo! User Interface Blog (YUIBlog). [...]
August 25, 2010 at 10:19 am
Kinda surprised you didn’t examine Netflix’s implementation of a ratings widget…?
August 25, 2010 at 3:26 pm
@Jim
I’m not a Netflix user so I didn’t even think about it, but that’s a good point.
Too bad they want me to sign up for a month before I can take a look at their solution. I may do that though as you made me curious ;)
August 26, 2010 at 10:06 am
Thanks for the article! It seems that radio buttons come in handy often times when thinking about accessibility. We had an issue recently where we had a dropdown filter for search (search Images, search Wikipedia, etc.) where it made perfect sense accessibility-wise to code it with radio buttons. Even though visually it definitely wasn’t the first thought to use radio buttons…
By the way, here’s Netflix’s code for star ratings (I hope this code doesn’t get garbled…):
<a rel="nofollow" class="rv5" title="Click to rate the movie "Loved It"" href="…" tabindex="0">Rate 5 stars</a>
August 27, 2010 at 6:32 am
I don’t think you’ve really understood the Microformats approach. It’s entirely possible to have half values and you don’t need to have ‘stars’. This should be entirely acceptable:
<span class=”rating”><img src=”3-5-stars.png” alt=”3.5″></span>
There is a limitation that you can only rate 1.0 to 5.0.
August 27, 2010 at 7:29 am
@David: Thanks :)
@Jim:
Looking at the snippet David posted, it seems Netflix does not do anything special when it comes to markup/css. They use links and as far as I can tell they use a background image technique which comes with the issues I mention in this article.
Of course, from that snippet I can’t tell about behavior and fall back mechanisms…
One thing that seems strange to me though is the use of tabindex=”0″ on a link with a href attribute (unless of course they toggle the value to “disable” the link).
@Rob:
I believe you missed the point. If I mention Microformats in this article it is for their “image-less” approach, for their use of characters (*) instead of images. In which case, it is not possible to represent half values and you’re pretty much stuck with asterisks (“*”).
As a side note, the solution you posted would require nothing less than nine images.
I think a better approach would be to:
1. use an image in which the stars are transparent
2. wrap the image in a span
3. apply a background color to the span (that will show through the stars)
4. size the span according to the rating
This way, with a single image it should be possible to represent all possible states, from 0 to 5, including half values.
In this case, like in my example, there should be no “alt” value set, plain text should convey the value.
@David, @Jim, @Rob: Thanks for your feedback
August 27, 2010 at 8:16 am
Sorry, the image has confused things because I was trying to make two points with a single example – I’ll try to explain again.
The (*) characters in your example are irrelevant because the value comes from the title attribute on the abbr element. This is the abbr-design-pattern. So these:
<abbr class=”rating” title=”3.5″>3 and half stars</abbr>
<abbr class=”rating” title=”3.5″>70%</abbr>
<abbr class=”rating” title=”3.5″>donkey</abbr>
All indicate a rating of 3.5 out 5.0. Of course, unfortunately, so do these:
<abbr class=”rating” title=”3.5″>***</abbr>
<abbr class=”rating” title=”3.5″>******</abbr>
However you could mark up 3.5 stars using characters as long as you had a suitable character to mark a ‘half star’, for example:
<abbr class=”rating” title=”3.5″>***x</abbr>
I should mention the rest of the article is great, and I’ll stop harping on about Microformats now :)
August 27, 2010 at 8:56 am
@Rob
Please don’t get me started on the abbr-design-pattern ;)
The way I understand the ABBR element is that the content between the tags is the abbreviation and the title attribute is used for the expansion.
The characters in my example *is* the main data. The title is not only relevant to Microformats but to other users so the relationship between the two is important. The associated title will not confuse mouse users (tooltip) nor SR users who have chosen to read title values.
In my example, “***” is the abbreviation of “three stars”.
In yours, “***”, “****”, donkey, etc. are all the abbreviation of “three and a half stars”. Does that make sense outside of Microformats?
I’m sorry, but imho the way Microformats use ABBR is nothing more than a hack. It may work well for Microformats but it lowers the quality of the document for many users :-(
August 27, 2010 at 9:20 am
Yes it was, but my point was it doesn’t have to be. As far as the Microformat is concerned the text content of the element is irrelevant.
Well donkey could be if you had some wacky site with a rating system based on animal names, but the other two were supposed to be bad examples.
Yes they are, but it was your original example which used it. My original example, where I was attempting to show that fractional values were indeed possible, didn’t. But then you focussed on the use of an image instead of the point, so I tried to make the same point based on your example instead.
I’m just trying to point out to you that two of your three cons are not a limitation of the microformat you were demonstrating. I didn’t make any claims about the accessibility or otherwise of Microformats, which may be why you seem to be misunderstanding me.
If you don’t want to use ABBR, use value-title instead, either way it is possible to represent fractional rating values with Microformats and they do ‘work’ with stuff other than asterisks (though not necessarily in a fashion compatible with accessibility).
I really will shut up this time, because I know the tiny Microformat example at the beginning wasn’t the point at all.
August 31, 2010 at 7:05 am
I’m sorry, I dont read the whole article, just the markup for presentation and skipped all the comments, but I got an idea, that makes use of HTML5.
Actually, we only would then need one element:
The rest can be done by CSS(3). I have two ideas:
1) This is based on a sprite, containing 3 items: A yellow star, a half star and a grey star. With multiple backgrounds, you can build it all up. First background would be to repeat the yellow star, than the half star if needed, then they grey star. Even though this markup needs very much use of CSS and heavy selectors:
meter.rating[value="2"] {…} with all the different value=”x” selectors.
2) This is also based on a sprite, with two rows:
First row: 5 yellow, 5 grey stars
Second row: 4.5 yellow, 4.5 grey stars
The selectors would still be the same as above, but it’d would be easier to get together the background. Use either the first or second row of the sprite, then with background-position shift the row to its desired position, that it displays the correct stars.
It is still possible to implement it this way, probably with a javascript hack (document.createElement(“meter”)). Although accessibility tools will take some time to read this, we will have a clean markup. So some thought of the present future :)
August 31, 2010 at 7:09 am
Ou, the better markup would be like:
3.5 out of 5
or whatever one will put in meter or the title attribute.
August 31, 2010 at 7:11 am
Harch, the markup gots stripped out, damn
<meter class=”rating” min=”1″ max=”5″ value=”3.5″ title=”Stars”>3.5 out of 5</meter>
Hope this works now
September 2, 2010 at 4:39 am
Nice article, but do not forget about push button “Vote”, which you should hide with js later.
September 3, 2010 at 3:53 pm
@Roman
Did you check the demo page? The bottom of the demo page? ;-)
Thanks for your feedback
@Gossi
This article is about creating a solution that works across browsers and under various conditions (high contrast styles sheets, image off, JS off, etc.).
I’m not sure one could achieve this through the use of HTML5 elements, attribute selectors, and sprite images. At least not yet ;-)
Thanks for your comments
April 28, 2011 at 11:53 am
[...] year, Thierry Koblentz published an interesting article on the Yahoo! User Interface Blog called Developing an Accessible Star Ratings Widget which took a close look at the star rating widget. In that article, Thierry did a great job [...]
April 28, 2011 at 12:23 pm
Thierry, using your solution as a guideline I tried a slightly different technique which I was hoping to get your feedback on…
http://www.lifeathighroad.com/web-development/a-simple-and-accessible-star-rating-widget/
April 28, 2011 at 1:52 pm
@Mike
I did that on your blog
May 3, 2011 at 8:09 am
this looks great. will implement this on one of my projects and reduce the http requests