16
Star-Rating Using A Single Input
Yesterday I read InhuOfficial's post about star-rating, 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-hack to select the previous elements on :hover
.
On Codepen, you'll find more examples.
But it made me think: Is there another, perhaps simpler way, to create a rating-control?
Earlier this year, I did this image compare, 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
(andvalueAsNumber
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.
<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 first thing we need, is a star:
--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:
--fill: gold;
--fillbg: rgba(100, 100, 100, 0.15);
And finally, we need some default sizes and values:
--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:
.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:
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:
[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:
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 usingbox-shadow
can be used. The example above has been updated to include both types. You can also set it toreadonly
, 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
16