Star-Rating Using A Single Input
Tags: html, css, javascript, webdev
Published: 2021-07-03 10:56:03

Yesterday I read InhuOfficial's [post about star-rating](https://dev.to/inhuofficial/5-star-rating-system-actually-accessible-no-js-no-wai-aria-3idl), using a group of `<input type="radio">`-controls. Go read that for some great accessibility-insights.

I did something similar a couple of years ago, also using radio-buttons, but with the [unicode:bidi / direction](https://css-tricks.com/examples/StarRating/)-hack to select the previous elements on `:hover`.
On Codepen, you'll find [more examples](https://codepen.io/lsirivong/pen/ekBxI).

But it made me think: Is there another, perhaps simpler way, to create a rating-control?

Earlier this year, I did this [image compare](https://codepen.io/stoumann/pen/VwKgJNv), where a single `<input type="range">` controls two `clip-path`'s.

That would also work as a rating-control, where the “left” image is the “filled stars” and the “right” image is the “unfilled stars”.

**What are the advantages of using an `<input type="range">`?**

- It's keyboard-accessible, can be controlled with all four arrow-keys
- It's touch-friendly
- It returns a `value` (and `valueAsNumber`in JavaScript), great for both visual browsers and screen-readers.

Let's dive into how we can use an `<input type="range">` for a rating-control. We'll make one, where you can easily add more stars, use half or even quarter-star rating, customize the star-colors etcetera.

---

## The HTML

```html
<label class="rating-label">
  <strong>Rating</strong>
  <input
    class="rating"
    max="5"
    oninput="this.style.setProperty('--value', this.value)"
    step="0.5"
    type="range"
    value="1">
</label>
```

The `max` is used for ”how many stars”. The `step` is `1` by default, but in this case, it's been set to `0.5`, allowing “half stars”. The `oninput` can be moved to an `eventListener`, if you want. It returns the current `value` and sets it as a “CSS Custom Property”: `--value`.

---

## The CSS
The first thing we need, is a star:

```css
--star: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 17.25l-6.188 3.75 1.641-7.031-5.438-4.734 7.172-0.609 2.813-6.609 2.813 6.609 7.172 0.609-5.438 4.734 1.641 7.031z"/></svg>');
```

This is an SVG, used in a CSS `url()`, so we can use it as a `mask` in mutiple places.

The `fill` of the stars and the default background-fill (when a star is **not** selected) are set as properties too:

```css
--fill: gold;
--fillbg: rgba(100, 100, 100, 0.15);
```

And finally, we need some default sizes and values:

```css
--dir: right;
--stars: 5;
--starsize: 3rem;
--symbol: var(--star);
--value: 1;
--x: calc(100% * (var(--value) / var(--stars)));
```

The `--x` variable is essential, as this indicates the “cutting point” in the gradient, we'll use in the “track” of the range-slider:

```css
.rating::-webkit-slider-runnable-track {
  background: linear-gradient(to var(--dir), var(--fill) 0 var(--x), var(--fillbg) 0 var(--x));
  block-size: 100%;
  mask: repeat left center/var(--starsize) var(--symbol);
  -webkit-mask: repeat left center/var(--starsize) var(--symbol);
}
```

And that's basically it! The `linear-gradient` is “filling up” the stars with the `--fill`-color, while the `mask` is used to mask it as stars. 

But why the `--dir`-property in the `linear-gradient`?

That's because we can't set a *logical direction* in CSS-gradients, for instance:

```css
linear-gradient(to inline-end, ...)
```

… does **not work** (yet!). Therefore, in order to make it work with “right-to-left”-languages, we need the `--dir`-property:

```css
[dir="rtl"] .rating {
  --dir: left;
}
```

In this case, when the dir is `rtl`, the gradient will be “to left”.

Here's a Codepen demo – notice how easy it is to add more stars, and how you can “drag” it as a slider:

{% codepen https://codepen.io/stoumann/pen/yLbYOdz %}

> **UPDATE:** People have requested a non-JS version, although the JS is only 45 bytes. Chrome does not support `range-progress` (like Firefox), but a hack using `box-shadow` can be used. The example above has been updated to include both types. You can also set it to `readonly`, if you want to show an “average review rating” like the last of the examples above.

---

And – to honor InhuOfficial: 

Thanks for reading!

---

*Cover-photo by Sami Anas from Pexels*