21
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
On Codepen, you'll find more examples.
: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">
?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.<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 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
21