--- name: accessibility-checklist description: | Accessibility review checklist for React/Next.js components built on Radix UI / shadcn/ui. Covers component library misuse, form accessibility, accessible names, keyboard interaction, focus management, and dynamic content. Loaded by pr-review-frontend. user-invocable: false disable-model-invocation: true --- # Accessibility Review Checklist ## How to Use This Checklist - Review changed components against the relevant sections below - Not every section applies to every component — form checks only apply to form components, modal checks only apply to modals, etc. - **This codebase uses Radix UI / shadcn/ui extensively.** These libraries handle most a11y patterns (keyboard nav, focus management, ARIA) automatically. Your primary job is to catch **misuse** of the library, not absence of manual implementation. - When unsure whether a component library handles a pattern, lower confidence rather than asserting --- ## §1 Component Library Misuse (Radix / shadcn/ui) This is the **highest-signal section** for this codebase. Radix handles a11y correctly when used correctly — bugs come from misuse. - **Dialog/Sheet without title**: Radix `Dialog` and `Sheet` require `DialogTitle` / `SheetTitle` for screen reader announcement. If `DialogTitle` is omitted or visually hidden without `aria-label` on `DialogContent`, screen readers announce an unlabeled dialog. - Common violation: `` with no `` and no `aria-label` - Note: Using `...` is a valid pattern for dialogs where a visible title doesn't fit the design - **AlertDialog without description**: `AlertDialogContent` should include `AlertDialogDescription` for screen readers to understand the confirmation context. If omitted, add `aria-describedby={undefined}` to explicitly opt out (otherwise Radix warns). - **Select/Combobox without accessible trigger label**: Radix `Select` needs `aria-label` on the trigger when there's no visible label. Custom `generic-select.tsx` and `generic-combo-box.tsx` wrappers should propagate labels. - Common violation: `` used standalone without any label - **Error messages must be associated with their input**: shadcn/ui's `` auto-associates via `aria-describedby` when inside ``. Custom error rendering outside this pattern loses the association. - Flag: Error text rendered near an input but not using `` or manual `aria-describedby` - **Required fields must be indicated programmatically**: Use `aria-required="true"` or native `required`, not just a visual asterisk. The `Form` component doesn't add this automatically — it comes from the Zod schema validation at submit time, not at the HTML level. - **Grouped controls need group semantics**: Radio groups and checkbox groups should use `` (Radix) or `
`/``. Loose radio buttons or checkboxes without group context confuse screen readers. - Scope: Configuration pages, settings forms, multi-option selectors --- ## §3 Accessible Names (Icons & Buttons) With 48 shadcn/ui components and heavy icon usage (Lucide React), icon-only interactive elements are a primary risk area. - **Icon-only buttons must have `aria-label`**: Buttons containing only an icon (no visible text) need `aria-label` describing the action. - Common violation: `` without `aria-label` - Very common in: data tables (row actions), toolbars, card headers, dialog close buttons - Note: shadcn/ui's `Dialog` close button already includes `Close` — don't flag this - **Icon-only links need accessible names**: Same as buttons — `` or `` with only an icon needs `aria-label`. - **`sr-only` text is a valid alternative to `aria-label`**: `` is correct. Don't flag this pattern as missing a label. - **Decorative icons should be hidden**: Icons that are purely decorative (next to visible text) should have `aria-hidden="true"` to avoid redundant announcements. - Correct: `` - Also correct: Lucide icons may set `aria-hidden` by default — check before flagging --- ## §4 Semantic HTML & Regression Guard The codebase currently has no `
` anti-patterns. This section guards against regressions. - **Interactive elements must use native interactive HTML**: `