# Star rating input is accessible and keyboard-navigable

- **Pattern:** `ab-001202` (`ecommerce-reviews.review-collection.star-input-accessible`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001202
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001202)

## Why it matters

WCAG 2.2 SC 4.1.2 (Name, Role, Value) and SC 2.1.1 (Keyboard) require that all interactive controls be operable without a mouse and expose their purpose to assistive technology. A star rating built from styled `<div>` elements with only `onClick` handlers is invisible to screen readers and completely unreachable by keyboard — failing Section 508 2018 Refresh 502.3.1 as well. Users who navigate by keyboard or rely on VoiceOver/NVDA cannot leave a rating at all, which is a measurable drop in review volume and a legal exposure under ADA and EAA for US and EU stores.

## Severity rationale

Medium because the inaccessible input excludes keyboard and screen-reader users from submitting reviews without enabling a security or data-integrity attack.

## Remediation

Replace click-only star elements with native radio inputs inside a `role="radiogroup"` container in `components/AccessibleStarInput.tsx`. Native radios give keyboard navigation (arrow keys), visible focus, and screen-reader announcements for free.

```tsx
// components/AccessibleStarInput.tsx
export function StarInput({ value, onChange }: { value: number; onChange: (v: number) => void }) {
  return (
    <div role="radiogroup" aria-label="Star rating">
      {[1, 2, 3, 4, 5].map(star => (
        <label key={star} className="star-label">
          <input
            type="radio"
            name="rating"
            value={star}
            checked={value === star}
            onChange={() => onChange(star)}
            aria-label={`${star} star${star > 1 ? 's' : ''}`}
            className="sr-only"
          />
          <span aria-hidden="true">★</span>
        </label>
      ))}
    </div>
  )
}
```

Add `CSS focus-visible` styles on `.star-label:has(input:focus-visible)` so the focus ring is visible without cluttering mouse interactions.

## Detection

- **ID:** `star-input-accessible`
- **Severity:** `medium`
- **What to look for:** Before evaluating, quote the star rating component's JSX or HTML markup. Count the number of accessibility features present: (1) aria-label or aria-labelledby, (2) role="radiogroup" or radio inputs, (3) keyboard navigation (onKeyDown handler or native radio behavior), (4) visible focus indicator (outline, ring, or border style on focus).
- **Pass criteria:** The star rating input has at least 3 of 4 accessibility features: ARIA labels (`aria-label`, `role="radio"` or `role="radiogroup"`), keyboard navigation via arrow keys or Tab+Enter, and a visible focus indicator with CSS focus styles. Report even on pass: state which 3-4 of 4 features are present.
- **Fail criteria:** The star rating input has fewer than 3 of 4 accessibility features, is click-only with no keyboard navigation, or lacks ARIA labels/roles making it invisible to screen readers.
- **Skip (N/A) when:** No review submission form exists (submission-form-exists already failed or skipped).
- **Detail on fail:** `"Star rating has 1 of 4 accessibility features (aria-label only). Missing: role=radiogroup, keyboard handler, focus indicator."` or `"Stars are styled divs with onClick only — 0 of 4 accessibility features."`
- **Remediation:** Build an accessible star input in `components/AccessibleStarInput.tsx` using radio buttons or ARIA roles:

  ```tsx
  // components/AccessibleStarInput.tsx
  export function StarInput({ value, onChange }) {
    return (
      <div role="radiogroup" aria-label="Rating">
        {[1, 2, 3, 4, 5].map(star => (
          <label key={star}>
            <input
              type="radio"
              name="rating"
              value={star}
              checked={value === star}
              onChange={(e) => onChange(parseInt(e.target.value))}
              aria-label={`${star} stars`}
            />
            <span>★</span>
          </label>
        ))}
      </div>
    )
  }
  ```

## External references

- wcag 4.1.2
- wcag 2.1.1
- section-508 502.3.1

Taxons: accessibility

HTML version: https://auditbuffet.com/patterns/ab-001202
