One thing I’ve always struggled with is coming up with a simple, elegant, lightweight way to display a product review summary. The issue here is that you frequently need to display a fraction of a star. The common way to do this is to have three images, a full, empty, and half filled star. But this has always felt wasteful and dirty to me. Why load three images while showing a misleading approximation (what do you display for a product with a 3.75 average rating? 3.9? You either lie, or sell your self short).
I had recently learned about custom CSS properties when I was tasked with building a new rewards summary. Inspiration struck and I think I was able to come up with a really satisfying solution:
The structure for the solution here is an element,
.stars, with a style attribute that sets a custom property for the product rating. It also has an aria-label set so that screen readers have access to the same information being depicted by our graphical elements (the stars).
The solution uses three custom properties:
- –_stars: This is a property that holds the number of stars you are trying to display. If a product is rated 3.75 out of 5 stars, this number is 3.75. This value is overwritten in the html.
- –_totalStars: This is how many total stars the rating is on, or 5 from the previous example.
- –_starSize: This sets the width of each star that is displayed.
From there we dive into the pseudo elements. Both the before and after are very similar so we can define them together. Give them empty content, assign them a background image of your star image. Set the background size based on the defined size custom property. Make the background repeat. And finally set the height of the element to the height of the star image and make them display inline-block so that our elements will sit next to each other.
A quick side note on the star icon. These can be supplied a number of ways. Probably the simplest method would be to use font awesome if you are already using it elsewhere on your site. But in my case I’m using an svg image of a star set to #010101 (note that it is not solid black, as solid black can’t be brightened with a filter) and then colorizing it using the technique explained here: https://css-tricks.com/solved-with-css-colorizing-svg-backgrounds/
Now for the interesting part. The goal here is to make the before element exactly wide enough to show the correct number of stars. This can easily be done with calc(). Just multiply the star rating by the star width. If you want to show 3.5 stars, then an element that is 3.5 times the width of the star image will fit exactly 3.5 stars.
Getting the “greyed out” stars is a little more complicated but very similar. In this case we want to show whatever is remaining. So we subtract the number of stars from the total and then use the same calculation as before. (5 stars minus 3.5 stars is 1.5 stars, multiply the width of the stars by 1.5 and you’ll get an element that is perfectly wide enough to show only 1.5 stars. Then we need to set the background-position to right so that the stars align correctly and give it an opacity to make it “greyed out”.
Take It Further
This is a great base to start from, but there is a lot you can do from here:
- Make stars interactive and change color on focus/hover
- Allow stars to be clicked to set the user’s rating
I really liked this solution because it made use of something brand new that I’d never used before and something old that I haven’t used in forever (background-repeat). And the two techniques came together to perfectly and cleanly solve a problem that I’ve never been quite happy with in the past.