Every interactive element must be operable via keyboard alone (Tab, Enter, Space, Escape). Users with motor disabilities, RSI, or broken trackpads cannot use a mouse — if your app requires mouse interaction, those users are completely locked out.
Keyboard-inaccessible interfaces completely exclude users with motor disabilities, repetitive strain injuries, or situational impairments (broken mouse, using a TV remote). This affects millions of users and violates WCAG 2.1 Level A (the minimum legal standard). In the US, inaccessible websites have resulted in ADA lawsuits with six-figure settlements.
BeforeMerge scans your pull requests against this rule and 4+ others. Get actionable feedback before code ships.
Not everyone uses a mouse. Users with motor disabilities use keyboard navigation, switch devices, or voice control — all of which rely on the keyboard interaction model. Users with repetitive strain injuries, broken trackpads, or even power users who prefer keyboard shortcuts all depend on Tab/Enter/Space/Escape working correctly.
When interactive elements are not keyboard-accessible, these users are completely locked out. They cannot click a button, open a dropdown, dismiss a modal, or submit a form. The application is unusable.
This is also a legal requirement. WCAG 2.1 Level A criterion 2.1.1 (Keyboard) states that all functionality must be operable through a keyboard interface. Failure to meet this is the basis for most web accessibility lawsuits.
Every interactive element must be reachable via Tab, activatable via Enter or Space, and dismissible via Escape where applicable. Focus must be visible at all times. No keyboard traps — users must be able to Tab away from any element.
// Not focusable, not keyboard-activatable
<div className="dropdown-trigger" onClick={() => setOpen(!open)}>
Select an option
</div>
{open && (
<div className="dropdown-menu">
{options.map(opt => (
// Not focusable, no keyboard handler
<div key={opt.id} onClick={() => selectOption(opt)}>
{opt.label}
</div>
))}
</div>
)}// Focusable, keyboard-activatable, proper ARIA
<button
aria-haspopup="listbox"
aria-expanded={open}
onClick={() => setOpen(!open)}
onKeyDown={(e) => {
if (e.key === 'Escape') setOpen(false);
if (e.key === 'ArrowDown' && !open) setOpen(true);
}}
>
Select an option
</button>
{open && (
<ul role="listbox" aria-label="Options">
{options.map((opt, i) => (
<li
key={opt.id}
role="option"
tabIndex={0}
aria-selected={selected === opt.id}
onClick={() => selectOption(opt)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') selectOption(opt);
if (e.key === 'Escape') setOpen(false);
}}
>
{opt.label}
</li>
))}
</ul>
)}Manual testing is the most reliable method:
Automated: search for click handlers on non-interactive elements:
grep -rn 'onClick' --include="*.tsx" | grep '<div\|<span\|<li'<div onClick> and <span onClick> with <button> or <a> elementsonKeyDown handlers for custom interactive widgets (dropdowns, modals, tabs)outline: none without a replacement)