Build a star rating
easyAccessibility+150 XP
Render five stars a person can rate something with, 1 through 5.
Hovering previews the rating (that star and every one before it lights up) without committing anything. Clicking commits the rating.
Examples
// Initial: rating = 0, no stars filled.
// Hover star 4
// → stars 1..4 get data-preview="true"; rating-value still "0".
// Click star 3
// → rating-value = "3"; stars 1..3 data-filled="true".
// Click star 5 → rating "5", 1..5 filled.
// Click star 2 → rating "2", only 1..2 filled (3..5 clear).Preview and committed state are independent; re-rating down must clear higher stars.
const filled = n <= rating; // committed
const preview = hovered !== null && n <= hovered; // transient
<button
aria-label={`Rate ${n} star${n === 1 ? "" : "s"}`}
data-filled={filled}
data-preview={preview}
onMouseEnter={() => setHovered(n)}
onClick={() => setRating(n)}
/>Two independent flags per star — the hover state never writes to the committed rating.
What it has to do
- Each star is a `<button>` with `aria-label="Rate 1 star"` for the first star and `aria-label="Rate {n} stars"` for the rest.
- Each star has `data-filled="true"` or `"false"` reflecting the committed rating, and `data-preview="true"` or `"false"` reflecting whichever star is currently hovered (and every star before it) — independent of the committed rating.
- An element with `data-testid="rating-value"` shows the committed rating as plain text ("0" before anything is clicked).
- Hovering must never change the committed rating shown in rating-value.
- Re-rating (clicking a different star after one is already committed) must correctly update which stars are filled — both up and down.
Notes
- Keep `rating` and `hovered` as separate state. Collapsing them into one "effective rating" during hover makes the committed value jump mid-preview.
- Reset `hovered` to `null` on `onMouseLeave` of the row — otherwise the last-hovered star stays highlighted after the cursor leaves.
- Keyboard support matters: each star is already a `<button>`, so Tab + Enter works. Add Left/Right arrow key support to rate without a mouse.
- Give the group a label (`role="radiogroup"` + `aria-label="Rating"`) so a screen reader announces "Rating, 5 stars" rather than five anonymous buttons.
Stuck? The react course covers the ideas this problem is built on.
import { useState } from "react"; export default function App() { const [rating, setRating] = useState(0); const [hovered, setHovered] = useState(null); return ( <div style={{ fontFamily: "sans-serif", padding: 16 }}> <div style={{ display: "flex", gap: 4 }}> {[1, 2, 3, 4, 5].map((n) => { // TODO: `filled` should reflect the committed rating, and // `preview` should reflect the hovered star (and everything // before it), independent of the committed rating. const filled = false; const preview = false; return ( <button key={n} aria-label={`Rate ${n} star${n === 1 ? "" : "s"}`} data-filled={filled} data-preview={preview} onMouseEnter={() => setHovered(n)} onClick={() => setRating(n)} style={{ fontSize: 24, border: "none", background: "none", cursor: "pointer", color: filled || preview ? "#f5a623" : "#ccc", }} > {"\u2605"} </button> ); })} </div> <p> Rating: <span data-testid="rating-value">{rating}</span> </p> </div> ); }
- 1.Each star is a `<button>` with `aria-label="Rate 1 star"` for the first star and `aria-label="Rate {n} stars"` for the rest.
- 2.Each star has `data-filled="true"` or `"false"` reflecting the committed rating, and `data-preview="true"` or `"false"` reflecting whichever star is currently hovered (and every star before it) — independent of the committed rating.
- 3.An element with `data-testid="rating-value"` shows the committed rating as plain text ("0" before anything is clicked).
- 4.Hovering must never change the committed rating shown in rating-value.
- 5.Re-rating (clicking a different star after one is already committed) must correctly update which stars are filled — both up and down.
5 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
5 tests · +150 XP
Discussion
Sign in to postSign in to join the conversation.Sign in
No comments yet. Be the first to say something.