DEV3 Technologies home

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">&#9733;<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">&#9733;<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">&#9733;<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">&#9733;<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">&#9733;<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-400 to your brand colour.
  • Read the value server side from the rating field 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 name on one page for two forms merges them, so give each form its own name or put them in separate forms.
  • Without required an empty rating submits nothing at all.

How to use it

  1. Paste the fieldset into your review form.
  2. Keep the name attribute as the field your controller validates.
  3. Use ids that are unique on the page if the component appears more than once.
  4. 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.

Related snippets