Tiny component, deceptive depth: hover states, keyboard access, persistence. The accessible version uses radio buttons โ free keyboard support, free form integration.
The radio technique
<fieldset class="stars"> <legend>Rate this course</legend> <input type="radio" name="rate" id="r5" value="5"><label for="r5">โ </label> <input type="radio" name="rate" id="r4" value="4"><label for="r4">โ </label> <input type="radio" name="rate" id="r3" value="3"><label for="r3">โ </label> <input type="radio" name="rate" id="r2" value="2"><label for="r2">โ </label> <input type="radio" name="rate" id="r1" value="1"><label for="r1">โ </label> </fieldset>
.stars { display: flex; flex-direction: row-reverse; border: 0; } /* reversed! */
.stars input { position: absolute; opacity: 0; }
.stars label { font-size: 1.8rem; color: #3a3a4a; cursor: pointer; }
/* the trick: hover colors THIS star and all AFTER it (visually: before) */
.stars label:hover, .stars label:hover ~ label,
.stars input:checked ~ label { color: #eab308; }The row-reverse + sibling-selector combo does hover-fill with zero JS. Radios mean arrow keys work and the value submits with the form.
Read-only display (for showing averages)
.rating-display {
--value: 4.3;
background: linear-gradient(90deg, #eab308 calc(var(--value) / 5 * 100%), #3a3a4a 0);
-webkit-background-clip: text; color: transparent;
}
<div class="rating-display" style="--value: 4.3">โ
โ
โ
โ
โ
</div>Gradient-clipped text = exact fractional ratings, one div. Persist choices to localStorage and you have a complete review widget.
โ All Articles