How to Make Easy & Flexible Star Ratings
Here’s a quick tip explaining how to create some nice star ratings.
Setting the Stage
The goal is to avoid having a sprite that looks like this:
This method works, but has some drawbacks: it uses a lot of pixels, it’s a nightmare to maintain/update, and it forces you to commit to a set rating size.
Let’s do better.
Each star has two states: filled and empty. We’ll start here.
Here’s the sprite we’ll be working with (super sized stars courtesy of Airbnb’s marvelous designer Steph Tekano):
- Should support any number of stars. We use five star ratings right now, but another module could potentionally use ten stars or four stars later down the road.
- Should support fractional stars. We use half stars right now, but again, this could change later on and it should be easy.
There are two layers, the empty layer and the filled layer. The filled layer will sit on top of the empty layer.
We’ll use repeating background images to create the number of stars we want for both the empty and filled layers. The empty layer will be a fixed width based on the number of stars we’re rating. So for five stars, the width of the empty layer will be 5 x (the width of one star).
The filled layer will have a variable width that is dependent on what the rating is.
The magic happens in the styling.
Let’s start with the markup.
“filled” and “empty” are pretty common class names. To be extra careful that you don’t override any other selectors of the same name you can give them an additional class called “stars”.
Now let’s do some styling.
We use Sass beacuse it makes life easier. Some of the big benefits are Nesting, Variables, Functions, Importing, and Mixins. It compiles everything to CSS. I recommend it because you can get things done faster. Less time styling is a good thing.
The first step is to create some variables to specify the width of a star & background offset (from the sprite), the number of stars, and the number of steps (I used 2 because I want half star ratings).
Then I created a mixin called “filled” which figures out the width of the filled div based on the size of the star, the number of steps, and the rating number $n (default is 0). The power of the mixin is that you don’t have to do any math.
Both the filled and empty classes use /images/sprite.png as their background image. The empty class offsets the sprite vertically by the height of a star.
Here’s what the generated CSS looks like:
The Missing Piece
You can see from stars.scss that I created classes specific to the star rating: filled_5, filled_6, filled_7 etc. Those numbers are the star rating stored as attributes on whatever the model is. All we need to do is output that rating into the markup.
It would be a simple refactor to make a Rails helper method to output the markup with a call like <%= star\_rating(@obj.rating) %> in a view. For more bonus points you could turn stars.scss into a partial called _stars.scss and then @import it into other scss files when needed in other modules.
Lots of folks handle this sort of thing in different ways. This happens to be the way I like to do it because it’s flexible, tries not to repeat itself, and requires one small sprite. Hope it helps. Thanks for reading!