Star rating input with no JavaScript
An accessible five-star rating field made from radio buttons and sibling selectors, so it submits with any form and needs no script.
Tailwind CSSFormsratingstarsradioaccessibility
Live preview
Code
<!-- Pure-CSS star input: radios in reverse DOM order so sibling selectors can fill stars -->
<fieldset class="inline-flex flex-col gap-1">
<legend class="mb-1 text-sm font-medium text-slate-700">Your rating</legend>
<div class="flex flex-row-reverse justify-end focus-within:outline-2 focus-within:outline-indigo-500">
<input type="radio" id="star5" name="rating" value="5" class="peer sr-only" required>
<label for="star5" title="5 stars"
class="cursor-pointer text-3xl text-slate-300 transition peer-checked:text-amber-400 hover:text-amber-400 [&:hover~label]:text-amber-400">★<span class="sr-only">5 stars</span></label>
<input type="radio" id="star4" name="rating" value="4" class="peer sr-only">
<label for="star4" title="4 stars"
class="cursor-pointer text-3xl text-slate-300 transition peer-checked:text-amber-400 hover:text-amber-400 [&:hover~label]:text-amber-400">★<span class="sr-only">4 stars</span></label>
<input type="radio" id="star3" name="rating" value="3" class="peer sr-only">
<label for="star3" title="3 stars"
class="cursor-pointer text-3xl text-slate-300 transition peer-checked:text-amber-400 hover:text-amber-400 [&:hover~label]:text-amber-400">★<span class="sr-only">3 stars</span></label>
<input type="radio" id="star2" name="rating" value="2" class="peer sr-only">
<label for="star2" title="2 stars"
class="cursor-pointer text-3xl text-slate-300 transition peer-checked:text-amber-400 hover:text-amber-400 [&:hover~label]:text-amber-400">★<span class="sr-only">2 stars</span></label>
<input type="radio" id="star1" name="rating" value="1" class="peer sr-only">
<label for="star1" title="1 star"
class="cursor-pointer text-3xl text-slate-300 transition peer-checked:text-amber-400 hover:text-amber-400 [&:hover~label]:text-amber-400">★<span class="sr-only">1 star</span></label>
</div>
</fieldset>
How it works
What it solves
Rating fields are often built from clickable icons that break keyboard use, screen readers and plain form posts. Here the real control is a native radio group, so the value submits normally, validation works through required, and arrow keys move between options.
How it works
Stars are labels for visually hidden radios (sr-only, not display:none, so they stay focusable). The trick is order: the five inputs sit inside a flex-row-reverse container in descending value order, so visually they read 1 to 5 from left to right while the DOM puts star 5 first. Because CSS sibling selectors only look forward in the DOM, peer-checked and a [&:hover~label] variant on later-in-DOM labels, which are the lower stars, fill every star at or below the chosen one. Hover uses the same logic for a live preview.
Each label has hidden text such as "4 stars" for assistive technology, and the fieldset legend names the group.
Adapting it
- Swap the glyph for an inline SVG and keep the same colour classes.
- Change
text-amber-400to your brand colour. - Read the value server side from the
ratingfield as an integer between 1 and 5 and validate that range.
Pitfalls
- Reversing visual order with flex does not change tab order; arrow keys inside a radio group still work as expected.
- Repeating the same
nameon one page for two forms merges them, so give each form its own name or put them in separate forms. - Without
requiredan empty rating submits nothing at all.
How to use it
- Paste the fieldset into your review form.
- Keep the
nameattribute as the field your controller validates. - Use ids that are unique on the page if the component appears more than once.
- Validate server side that the value is an integer from 1 to 5.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.