| name | accessible-components |
| description | Use when building or reviewing interactive UI that must meet WCAG 2.2 AA — focus management, ARIA roles and states, keyboard navigation, labels, and visible focus. Covers the accessibility mistakes agents make most. |
Accessible Components
Overview
Accessibility is not a feature you add later — it's how you build interactive elements from the start. The single best move in a SaaS app is to use real semantic elements and Radix/shadcn primitives, which ship correct ARIA and keyboard behavior. Most a11y bugs come from re-inventing a button or dropdown with a <div>.
When to use
- Building any interactive control (menus, dialogs, tabs, toggles).
- Reviewing UI for keyboard and screen-reader support.
- A
<div onClick> is standing in for a button.
Rule 1: Use the right element
<div onClick={save} className="btn">Save</div>
<button type="button" onClick={save} className="btn">Save</button>
A native <button> is focusable, fires on Enter/Space, and announces as a button. Same for <a> (navigation), <label> (form association), <nav>/<main>/<header> (landmarks).
Rule 2: Every input has a label
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" />
<Button size="icon" aria-label="Close">
<X className="h-4 w-4" aria-hidden />
</Button>
Decorative icons get aria-hidden; the control gets aria-label.
Rule 3: Visible focus, always
Never outline-none without a replacement. Use a focus ring on every interactive element:
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
:focus-visible shows the ring for keyboard users without flashing it on mouse clicks.
Rule 4: Keyboard support
- All actions reachable via Tab; logical order (don't fight the DOM with
tabindex > 0).
Esc closes overlays; Enter/Space activate; arrow keys move within menus/tabs/radios.
- Radix/shadcn primitives (
DropdownMenu, Dialog, Tabs, Select) implement these — prefer them over hand-rolled widgets.
Rule 5: Announce dynamic changes
Screen readers miss silent DOM updates. Use live regions for async status:
<p role="status" aria-live="polite">{saving ? "Saving…" : "All changes saved"}</p>
Toasts should render in an aria-live region (sonner/shadcn handle this — see notifications-and-toasts).
Quick checklist
Pitfalls
<div onClick> instead of <button> — no keyboard, no role.
outline-none with no focus replacement — keyboard users get lost.
- Placeholder as label — disappears on input, fails screen readers.
- Color-only state (red border, no message) fails colorblind users.
- Hand-rolled dropdowns/modals missing focus trap and arrow keys — use primitives.
Hand-off
The accessibility baseline every other skill assumes — forms-and-validation, modals-and-dialogs, and navigation-patterns all build on these rules.