A product team adds a rating widget to a review card. The designer spec says “5 stars; click to rate; show the filled stars”. The junior engineer ships in fifteen lines — a row of five star glyphs, each with an onClick that sets a value state. The reviewer clicks through all five stars on a Chrome/macOS mouse session, watches them fill, nods. Merge. Ship it Friday.

Monday morning, three support tickets: a mobile user who “meant to tap 4 stars but got 5 and can't undo”; a keyboard user who “can't reach the stars at all — Tab skips the whole row”; a screen-reader user whose NVDA session reads “clickable, clickable, clickable” with nothing meaningful announced. Three users, one row of stars, same fifteen lines.

Live row · naive <div onClick>committed: 0 / 5
Try it: aim at 4, commit to 3. Can you cancel mid-click?no misclick yet

That row above is the fifteen-line version. Click a star — the value commits instantly. There is no hover feedback, no way to see “would rate 3 of 5” BEFORE your click fires. Try it: hover over 4 stars, decide to commit to 3 instead — but you can't cancel once your finger moves. Every click is a commit. That is the misclick wall.

FIG. 1 — THE JUNIOR'S STAR ROW
1
// The rating every junior ships first.
2
// Clicks work. Nothing else does.
3
function BrokenRating({ value, onChange }) {
4
  return (
5
    <div className="flex gap-1">
6
      {[1, 2, 3, 4, 5].map((n) => (
7
        <div
8
          key={n}
9
          onClick={() => onChange(n)}
10
          className={n <= value ? 'star filled' : 'star empty'}
11
        >
12
13
        </div>
14
      ))}
15
    </div>
16
  );
17
}
18
19
// What breaks:
20
//   1. No hover preview — user cannot see "would rate 3/5" before click.
21
//      Misclicks ship the wrong value. No undo.
22
//   2. <div> not focusable — keyboard Tab skips the entire row.
23
//   3. No role — NVDA reads "clickable, clickable" with no semantic
24
//      anchor. The value 3 is not announced at all.
25
//   4. No half-star support — a review worth 3.5 rounds to 3 or 4,
26
//      collapsing aggregate precision.
27
//   5. No readOnly mode — display-only ratings stay clickable; an
28
//      accidental re-rate fires from a stray tap.
— shipped as-is —
Step 1 / 3 · The misclick

Three probes on the naive row. A mouse user aiming at star 4 tapped star 5 by accident. A keyboard user pressed Tab. A screen-reader user focused the row. Which probes surface a real failure in the fifteen-line version?

Three users run three probes against the naive <div onClick> star row. Which ones break?

three probes
what's missing
the residue
done