Pure CSS Star Rating Input

This is a familiar view for most of web users:

And there are a lot jQuery plugins and plain JavaScript code that can help you create rating widget in your form. And the reason because it's all written in JavaScript is that when a need for such a rating system first arose CSS really lacked the ability to do stuff like this.

It's possible now!

But let's define a problem set first. It all boils down to three things:

  • It needs to work as an input element. You know, if you selected 4-star it needs to send something like "4" to the server when I click "submit".
  • You want not only highlight star that you hovered but all the stars before that.
  • After clicking a star selection must "stick" when none of the stars are hovered, but still change when they are hovered.

Let's start tackling this challenges one by one.

Basic operation

This is actually pretty easy. When you have a label for a form element, clicking on that label will cause a radio button or a checkbox to get checked. It is a good practice in general but it's absolutely essential here since we are going to use labels as the stars so clicking on a star would select a radio button with appropriate value.

Here's some all the HTML code we need:

<span class="rating">
    <input type="radio" class="rating-input"
        id="rating-input-1-5" name="rating-input-1">
    <label for="rating-input-1-5" class="rating-star"></label>
    <input type="radio" class="rating-input"
        id="rating-input-1-4" name="rating-input-1">
    <label for="rating-input-1-4" class="rating-star"></label>
    <input type="radio" class="rating-input"
        id="rating-input-1-3" name="rating-input-1">
    <label for="rating-input-1-3" class="rating-star"></label>
    <input type="radio" class="rating-input"
        id="rating-input-1-2" name="rating-input-1">
    <label for="rating-input-1-2" class="rating-star"></label>
    <input type="radio" class="rating-input"
        id="rating-input-1-1" name="rating-input-1">
    <label for="rating-input-1-1" class="rating-star"></label>
</span>

And the base CSS code where we hide inputs while keeping labels

  .rating {
      overflow: hidden;
      display: inline-block;
  }
  .rating-input {
      float: right;
      width: 16px;
      height: 16px;
      padding: 0;
      margin: 0 0 0 -16px;
      opacity: 0;
  }
  .rating-star {
      display: block;
      width: 16px;
      height: 16px;
      background: url('star.png') 0 -16px;
  }
  .rating-star:hover {
      background-position: 0 0;
  }

After that you have stars aligned vertically that can be hovered and if you click them it will actually save appropriate value but selection wouldn't "stick". We will deal with this later on.

Highlighting stars before current one

You might have noticed that inputs and labels in code are arrange in the reverse order and also all elements inside main .rating element are siblings (no hierarchy).

The reason we need this is because we are going to use CSS general sibling selector (~) and it only works for elements after the element targeted by the selector and as the name suggest only for siblings. Let's update our CSS to make use of that.

  .rating-star {
      position: relative;
      float: right;
      display: block;
      width: 16px;
      height: 16px;
      background: url('star.png') 0 -16px;
  }

  .rating-star:hover,
  .rating-star:hover ~ .rating-star {
      background-position: 0 0;
  }

We are using float:right to put elements horizontally in the right order with the highest on the right hand side. And use general sibling selector to target all the stars that are after currently hovered in code but appear before it visually.

Making selection "stick"

Basically we are going to use the same trick we used for my CSS3 image-free checkbox where CSS3 :checked selector was used but throw in general sibling selector in the mix too:

  .rating-star:hover,
  .rating-star:hover ~ .rating-star,
  .rating-input:checked ~ .rating-star {
      background-position: 0 0;
  }

Selection now does stick but there's still a problem. If you select a third star and try to hover second one you won't get the desired effect — third one will still be active.

We can fix that but reseting highlight when we hover wrapper .rating element and reenforce highlighting for the hovered ones:

  .rating:hover .rating-star:hover,
  .rating:hover .rating-star:hover ~ .rating-star,
  .rating-input:checked ~ .rating-star {
      background-position: 0 0;
  }
  .rating-star,
  .rating:hover .rating-star {
      position: relative;
      float: right;
      display: block;
      width: 16px;
      height: 16px;
      background: url('star.png') 0 -16px;
  }

That's it for modern browsers. For IE8 & IE7 you will have to use a JavaScript-based solution though and probably update some of the styles.

This solution can of course be extended by some click effects, animations and reset button that can all be built based on existing code.

Social comments Cackle