Talentedunicorn logo

Star rating component

Last updated

Recently when working on a project I came across a design that included rating information on the interface. The UI included some “stars” representing the rating with the value shown as the colored in part of the star and the total rating being the number of stars.

Rating stars

Rating stars

First thing first, the markup structure of the indicator HAD to be semantic and accesssible before anything else

Markup

Rating indicators have been around for a while on the Internet but there hasn’t been much standardization of the markup used in these elements. I did some digging and found different methods used by current popular websites.

For example Airbnb uses svg inside multiple spans

Airbnb rating

Airbnb rating
<span role="img" aria-label="Rating 5 out of 5">
  <span><svg></svg></span>
  <span><svg></svg></span>
  <span><svg></svg></span>
  <span><svg></svg></span>
  <span><svg></svg></span>
</span>

While Amazon uses the <i> element with a <span> for the label

Amazon rating

Amazon rating
<i class="a-icon">
  <span class="a-icon-alt">3.6 out of 5 stars</span>
</i>

But I needed something that would be semantic and accessible to users. I came across the <meter> element that had most of the semantic aspects; if you are new to the meter element it is described as “representing either a scalar value within a known range or a fractional value.”

Meter element represents either a scalar value within a known range or a fractional value.

The markup for the rating element would look something like this

<meter min="0" max="5" value="3.5">Rating 3.5 out of 5</meter>

By default, the meter element has a bar similar to the progress element.

Rating 3.5 out of 5

What we need is “stars” to represent our rating, we’ll get this done with the styles

Styling

The meter element is well supported across a lot of browsers (with exception of Internet Explorer at the time of this post). Styling the meter element is covered quite extensively on this article by Pankaj Parashar but I was not pleased with the number of vendor prefixes needed to change the look of the element.

In addition to consistent experience across browsers; maintaining semantics and accessibility is of high priority. According to the article; pseudo-elements for the meter element are only supported on webkit browsers so I decided to take a leaf out of amazon’s book and wrap the meter element in a <span> which resulted to the following markup:

<span class="meter" title="Rating 3.5 out of 5" style="--currentIndex: 3.5">
  <meter min="0" max="5" value="3.5">Rating 3.5 out of 5</meter>
</span>

Preparing the star graphic

For the stars, I found an svg which I had optimized and converted to a data-uri then used that as my background.

Binding the data to styles

To communicate the value between my markup and styles I used CSS variables with the rating value in an inline style attribute.

I would later on use that value to render the correct representation of the rating. Here’s how my styles looked like

/* Some variables */
:root {
  --size: 2rem;
  --max: 5;
  --inactive: gray;
  --active: yellow;
}

.meter {
  meter {
    display: none;
  }
  display: inline-block;
  width: calc(var(--size) * var(--max));
  height: var(--size);
  position: relative;

  &::before,
  &::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    mask: left center/contain;
    mask-image: var(--star);
  }

  &::before {
    background: var(--inactive);
  }

  &::after {
    content: "";
    z-index: 1;
    background: var(--active);
    right: calc(100% - (var(--currentIndex) / var(--max) * 100%));
  }
}

I needed two “layers”, one for the base stars which I used the &::before pseudo-element and for the active state I used the &::after. Below is a pen demonstration: