Source Code

<div class="rs-card">
  <p class="rs-q">How was your experience?</p>
  <div class="rs-stars" id="rsStars" role="radiogroup" aria-label="Rating">
    <button type="button" class="rs-star" role="radio" aria-label="1 star" data-v="1" aria-checked="false"><svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.5 5.9 20.4l1.5-6.8L2.2 9l6.9-.7z"/></svg></button>
    <button type="button" class="rs-star" role="radio" aria-label="2 stars" data-v="2" aria-checked="false"><svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.5 5.9 20.4l1.5-6.8L2.2 9l6.9-.7z"/></svg></button>
    <button type="button" class="rs-star" role="radio" aria-label="3 stars" data-v="3" aria-checked="false"><svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.5 5.9 20.4l1.5-6.8L2.2 9l6.9-.7z"/></svg></button>
    <button type="button" class="rs-star" role="radio" aria-label="4 stars" data-v="4" aria-checked="false"><svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.5 5.9 20.4l1.5-6.8L2.2 9l6.9-.7z"/></svg></button>
    <button type="button" class="rs-star" role="radio" aria-label="5 stars" data-v="5" aria-checked="false"><svg viewBox="0 0 24 24"><path d="M12 2l2.9 6.3 6.9.7-5.2 4.6 1.5 6.8L12 17.5 5.9 20.4l1.5-6.8L2.2 9l6.9-.7z"/></svg></button>
  </div>
  <p class="rs-out" id="rsOut">Tap a star to rate</p>
</div>

Rating Stars Input — Free HTML CSS JS Star Rating Picker

Rating Stars Input · Forms · Plain HTML, CSS & JS · Live preview

What's included

Features

Hover preview
Lighter fill previews before you commit.
Committed selection
Click locks in a brighter fill.
Fill-up-to logic
One count lights all prior stars.
SVG stars
Crisp, recolorable, scalable shapes.
Radiogroup ARIA
role=radio with aria-checked per star.
Keyboard control
Arrows move, Enter/Space commit.
Focus ring
:focus-visible outline for keyboard users.
Descriptive label
Shows score plus a word like Good.

About this UI Snippet

Rating Stars Input — A Hoverable, Keyboard-Friendly Star Picker

Screenshot of the Rating Stars Input snippet rendered live

The rating stars input is the five-star control people use to score a product, an order, or an experience — stars fill up to the one under your cursor as a preview, and clicking commits the score. This snippet builds an accessible version with SVG stars, CSS, and vanilla JavaScript, including a hover preview, keyboard support, and a live text label.

Two layers: hover preview and committed rating

The control tracks two separate states. Hovering a star paints a lighter "is-hover" fill on it and every star before it, previewing what you'd get if you clicked; clicking commits a brighter "is-on" fill that persists. Keeping preview and selection as different classes means moving the mouse away cleanly restores the committed rating instead of clearing it — the behaviour every star input should have but many get wrong.

Fill-up-to logic

Both states use the same idea: a paint(n, cls) helper toggles a class on stars whose index is less than n, so star three lights stars one through three. There's no per-star bookkeeping — the count drives which stars are lit. The stars themselves are inline SVGs whose fill and stroke transition, so the colour change is smooth and they scale slightly on hover for a tactile feel.

Accessible by design

The group is a role="radiogroup" and each star a role="radio" with an aria-label ("3 stars") and aria-checked that reflects the committed value, so screen readers announce it as a rating control rather than a row of buttons. A :focus-visible outline shows keyboard focus, and the live label text updates so the score is conveyed in plain text too.

Keyboard control

Arrow keys move focus between stars (Right/Up forward, Left/Down back, clamped at the ends) and Enter or Space commits the focused star — the standard radio-group keyboard model. This makes the input fully operable without a mouse, which is essential for a form field.

A descriptive label

Beyond the stars, a line below shows the numeric score and a word — "4/5 · Good" — pulled from a labels array. Pairing a number with a descriptor makes the rating clearer and gives immediate feedback that the click registered.

Customizing it

Change the star count, the colours, or the label words; pre-set a rating by calling setRating on load; or read rating on form submit. Swap the SVG path for hearts or thumbs for a different scale. Pair it with a star rating display, a review form, or a rating breakdown.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to untangle the two-layer state model by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why is-hover and is-on are kept as two separate CSS classes rather than one, and how that separation is what lets mouseleave clear only the preview while the committed rating stays lit. The same assistant can help you optimize it — ask whether the paint() helper re-toggling a class on all five stars on every single mouseenter is wasteful compared to only touching the stars whose state actually changed between the old and new count. It's also useful for extending the control: ask it to support half-star precision by detecting cursor x-position within a star, swap the star SVG path for hearts or thumbs while keeping the exact same fill-up-to logic, or persist and restore a prior rating from a data attribute on load. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an accessible star rating input in plain HTML, CSS, and JavaScript with no library — five SVG stars that preview on hover and commit on click.

Requirements:
- Structure the stars as a role="radiogroup" container with five button elements, each role="radio", each with an aria-label describing its position (e.g. "3 stars") and an aria-checked attribute that starts false.
- Keep hover preview and committed selection as two entirely separate CSS classes on the star elements (do not reuse one class for both), so that a shared "light up every star whose index is less than N" helper function can apply either class independently without one state overwriting the other.
- On mouseenter of a given star, apply the hover-preview class up to and including that star's position. On mouseleave of the whole group (not each individual star), clear only the hover-preview class — the committed selection class must remain untouched and still visibly lit.
- On click of a star, apply the committed class up to that star's position, update aria-checked to true only on the exact star matching the committed count and false on all others, and update a live text label showing both the numeric score and a word description (e.g. "4/5 - Good") pulled from a label array indexed by the score.
- Support full keyboard operation: ArrowRight/ArrowUp must move focus to the next star, ArrowLeft/ArrowDown to the previous star (both clamped at the ends so focus never leaves the group), and Enter or Space on a focused star must commit that star's rating exactly like a click.
- Give focused stars a visible :focus-visible outline distinct from the hover and committed fill colors.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSFive empty stars render with a prompt.
  2. 2
    Hover the starsThey fill up to the one under the cursor.
  3. 3
    Click to commitThe score sticks and the label updates.
  4. 4
    Use arrow keysMove focus and press Enter to rate.
  5. 5
    Move the mouse awayThe preview clears, the committed score stays.
  6. 6
    Read the valueGet the rating variable on submit.

Real-world uses

Common Use Cases

Review forms
Score input atop a review form.
Order feedback
Rate after an order summary.
Display vs input
Pair with a read-only star rating.
Aggregate views
Surveys
A scale step in an NPS survey.
Support

Got questions?

Frequently Asked Questions

The control keeps hover preview and committed selection as separate classes. Hovering paints a lighter is-hover fill, while clicking sets a persistent is-on fill. On mouseleave only the hover class is cleared, so the committed rating stays shown — the behaviour a star input should have but many implementations get wrong.

A paint helper toggles a class on every star whose index is less than the target count, so hovering or selecting star three lights stars one through three. The count drives which stars are lit, with no per-star bookkeeping, and the same logic serves both the hover preview and the committed rating.

Yes. The group is a role=radiogroup and each star a role=radio with an aria-label like 3 stars and aria-checked reflecting the committed value, so screen readers announce a rating control. Arrow keys move focus, Enter or Space commit, a focus-visible outline shows keyboard focus, and a live text label conveys the score in words.

Call setRating with the initial value on load, which applies the is-on fill to that many stars, sets aria-checked on the correct star, and updates the label. This is useful for edit forms where a user is changing an existing score rather than rating for the first time.

Hold rating and a hover value in state. Render each star lit when its index is less than the hover value (if hovering) or the committed rating otherwise, set onMouseEnter to the hover value, onMouseLeave to clear it, and onClick to commit. Keep the radiogroup roles and aria-checked bound to state. The SVG and CSS port unchanged.