Search any codebase for a star rating and you'll find five <div>s with onClick handlers. Mouse-only, invisible to screen readers, no form value, no keyboard. For a control whose entire job is collecting an opinion, it collects it from the narrowest possible audience.
Here's the reframe that fixes everything at once: a star rating is a radio group. Five options, exactly one selectable — the browser has shipped this control since 1995. Velocity X's rating-input is built on that bones.
The Money Pattern
Real radio inputs, visually hidden; the stars are the labels:
<fieldset>
<legend class="sr-only">Rate your experience</legend>
<div class="flex" onMouseLeave={() => setPreview(0)}>
{[1, 2, 3, 4, 5].map((n) => (
<label onMouseEnter={() => setPreview(n)}>
<input type="radio" name="rating" value={n}
checked={value === n}
onChange={() => setValue(n)}
class="sr-only" />
<Star filled={n <= (preview || value)} />
<span class="sr-only">{n} star{n > 1 && 's'}</span>
</label>
))}
</div>
</fieldset>
Everything hard arrives free: arrow keys move between stars (radio group semantics), Tab treats the group as one stop, screen readers announce "3 stars, radio button, 3 of 5", and the value submits with the form like any input. The only custom state is preview for the hover fill — a purely visual layer over a working control.
The Catch
Half-star ratings break the radio model's neat mapping — ten radios painted as five stars works, but the hover math gets fiddly and the keyboard step becomes 0.5. Ask whether half stars change any decision your product makes; display can be fractional (averages) while input stays whole.
The other trap is hover-commit: setting the actual value on mouseenter instead of click. A rating isn't chosen until it's clicked — preview and value are different states, and collapsing them turns a stray mouse movement into a 1-star review.
The Verdict
Reach for the platform control wearing custom paint, not a div sculpture re-implementing it badly. Radio group + sr-only + preview state is the whole component. It's in Velocity X's Forms category with the other inputs that look simple and aren't — source you own, restyled by your Tailwind config.









