Accessibility Enforcement
What This Catches
<img>withoutalt-- usealt=""for decorative img- Mouse-only
onClickon<div>/<span>-- needsrole+tabIndex+onKeyDown/onKeyUp - Missing ARIA on widget roles --
role="combobox"needsaria-expanded+aria-controls,role="dialog"needsaria-label/aria-labelledby,role="tablist"needs childrole="tab"
Escape hatch: // allow: a11y-skip [reason]
No Nested Pressables
Interactive components ONE pattern -- never both:
Pattern A: Container clickable -- no interactive children.
<ListCard
<Avatar src={user.avatar} />
<Text>{user.name}</Text>
<ChevronRightIcon /> {/* visual indicator only, not a button */}
</ListCard>
Pattern B: Children interactive -- container not clickable.
<ListCard>
<Avatar src={user.avatar} />
<Text>{user.name}</Text>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon"><MoreVerticalIcon /></Button>
</DropdownMenuTrigger>
</DropdownMenu>
</ListCard>
Why: ambiguous click targets, event bubbling bugs, screen readers can't convey interaction model, touch targets overlap on mobile.
Visual Checklist
- Focus rings visible on all interactive elements (min 2px, contrasting color)
- Hover and focus styles match (no mouse-only affordances)
- Color not sole means of conveying info
- Touch targets min 44x44 CSS pixels
-
prefers-reduced-motionrespected for animations -
forced-colors/ high-contrast mode: usecurrentcolorfor SVG fills - Text resizable to 200% without content loss
Initial setup (install, AXE fixture, hook config): see SETUP.md.