React Curriculum

Machine Coding: Accessible Star Rating Widget

Medium100% Free~30 mins#react#machine-coding#widgets#star-rating#accessibility#keyboard
Interview Answer (30-Second Summary)

An accessible Star Rating widget requires managing hover and selected states, full keyboard navigation with Arrow keys and Home/End, ARIA radiogroup semantics with role="radiogroup" and aria-checked, and dynamic visual state updates without DOM manipulation.

Key Learning Objectives
✓

Build an interactive Star Rating widget supporting 1 to N stars.

✓

Implement hover preview state that reverts to selected state on mouse leave.

✓

Ensure complete keyboard accessibility: ArrowLeft, ArrowRight, Home, End, and Space/Enter.

✓

Apply WAI-ARIA Radio Group pattern for screen reader accessibility.

The Interview Problem & Context

Implement an accessible, interactive `<StarRating max={5} value={3} onChange={(rating) => ...} />` component with hover feedback and full keyboard navigation.

1import React, { useState, KeyboardEvent } from 'react';
2
3interface StarRatingProps {
4 max?: number;
5 value: number;
6 onChange: (value: number) => void;
7 disabled?: boolean;
8}
9
10export function StarRating({
11 max = 5,
12 value,
13 onChange,
14 disabled = false,
15}: StarRatingProps) {
16 const [hoverValue, setHoverValue] = useState<number | null>(null);
17
18 // Active display rating is either current hover preview or committed value
19 const displayValue = hoverValue ?? value;
20
21 const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
22 if (disabled) return;
23
24 switch (e.key) {
25 case 'ArrowRight':
26 case 'ArrowUp':
27 e.preventDefault();
28 onChange(Math.min(max, value + 1));
29 break;
30 case 'ArrowLeft':
31 case 'ArrowDown':
32 e.preventDefault();
33 onChange(Math.max(1, value - 1));
34 break;
35 case 'Home':
36 e.preventDefault();
37 onChange(1);
38 break;
39 case 'End':
40 e.preventDefault();
41 onChange(max);
42 break;
43 }
44 };
45
46 return (
47 <div
48 role="radiogroup"
49 aria-label="Rating"
50 tabIndex={disabled ? -1 : 0}
51 onKeyDown={handleKeyDown}
52 onMouseLeave={() => setHoverValue(null)}
53 className="star-rating-container"
54 style={{ display: 'inline-flex', gap: '4px', cursor: disabled ? 'not-allowed' : 'pointer' }}
55 >
56 {Array.from({ length: max }, (_, index) => {
57 const starNumber = index + 1;
58 const isFilled = starNumber <= displayValue;
59
60 return (
61 <span
62 key={starNumber}
63 role="radio"
64 aria-checked={starNumber === value}
65 aria-label={`${starNumber} of ${max} stars`}
66 onClick={() => !disabled && onChange(starNumber)}
67 onMouseEnter={() => !disabled && setHoverValue(starNumber)}
68 style={{
69 fontSize: '28px',
70 color: isFilled ? '#f59e0b' : '#d1d5db',
71 userSelect: 'none',
72 transition: 'color 150ms ease-in-out',
73 }}
74 >
75 ★
76 </span>
77 );
78 })}
79 </div>
80 );
81}
Deep Architectural Explanation

Key Architecture for the Star Rating Widget

  1. State Partitioning:
    • value (committed state): passed as prop or controlled state.
    • hoverValue (preview state): local useState<number | null>(null).
    • displayValue = hoverValue ?? value: clean declarative derivation. On mouse leave, hoverValue resets to null, instantly snapping back to value.
  2. WAI-ARIA Accessibility:
    • Container has role="radiogroup" and tabIndex={0} to participate in keyboard navigation.
    • Each star has role="radio" with aria-checked={starNumber === value}.
    • Screen readers announce: "Rating, 3 of 5 stars, radio button checked."
  3. Keyboard Controls:
    • ArrowRight / ArrowUp: increment by 1 (clamped to max).
    • ArrowLeft / ArrowDown: decrement by 1 (clamped to 1).
    • Home / End: jump to minimum (1) or maximum (max).
Common Interview Traps & Pitfalls
✕

Mutating state on hover instead of using temporary hover state, causing accidental updates on mouse movement.

✕

Failing to handle keyboard arrow keys, leaving the widget inaccessible to keyboard-only users.

✕

Using `index` for rating calculation (0-indexed) without converting to 1-indexed stars.

Senior Interviewer Follow-Up Probes
?

How would you implement half-star rating rendering using SVG linearGradient or CSS clip-path?

?

How do you handle RTL (Right-to-Left) languages where ArrowLeft should increment rather than decrement?

?

How do you integrate this component into a React Hook Form or Formik form schema?

Frequently Asked Interview Questions
Why use hoverValue ?? value instead of directly overwriting state on hover?

Hovering is a visual preview, not a confirmed user selection. If the user moves their mouse away without clicking, the rating must return to the previously saved value.

role="radiogroup" on the container and role="radio" with aria-checked on each star element, or role="slider" with aria-valuemin, aria-valuemax, and aria-valuenow.

Add onKeyDown to the focusable container to handle ArrowUp/ArrowRight (increment), ArrowDown/ArrowLeft (decrement), Home (minimum), and End (maximum).

Use Math.max(1, value - 1) and Math.min(max, value + 1) during keyboard navigation updates.

Yes. Using hidden <input type="radio"> elements styled with CSS pseudo-elements is a classic semantic HTML approach that provides built-in keyboard navigation.

Add a disabled prop that sets pointer-events: none or disables hover/click handlers and sets tabIndex={-1} so keyboard focus skips the element.