Keyboard Navigation
All interactive elements must be accessible via keyboard.
Why
- Users with motor disabilities may use keyboard only
- Screen reader users navigate with keyboard
- Power users prefer keyboard shortcuts
Native Keyboard Support
Use semantic HTML elements that have built-in keyboard support:
| Element | Keyboard Behavior |
|---|---|
<button> |
Enter/Space to activate |
<a href> |
Enter to follow link |
<input> |
Tab to focus, type to input |
<select> |
Arrow keys to navigate options |
Bad - Non-interactive Elements as Buttons
// Bad - div is not keyboard accessible
<div onClick={handleClick} className="cursor-pointer">
Click me
</div>
// Bad - span with click handler
<span onClick={handleClick}>Action</span>Good - Semantic Elements
// Good - button is keyboard accessible
<button onClick={handleClick}>Click me</button>;
// Good - Button component from react-aria
import { Button } from "react-aria-components";
<Button onPress={handlePress}>Click me</Button>;When You Must Use Non-Semantic Elements
If you absolutely must use a non-semantic element, add keyboard support:
// Only when semantic elements aren't possible
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleClick();
}
}}
>
Click me
</div>But prefer using react-aria-components which handles this:
import { Button } from "react-aria-components";
// Renders as <div> but with full keyboard support
<Button onPress={handlePress}>Click me</Button>;Tab Order
Natural Tab Order
Elements are focused in DOM order. Structure your HTML logically:
// Good - logical order matches visual order
<form>
<input name="firstName" />
<input name="lastName" />
<input name="email" />
<button type="submit">Submit</button>
</form>Avoid Positive tabIndex
// Bad - arbitrary tab order is confusing
<input tabIndex={2} />
<input tabIndex={1} />
<input tabIndex={3} />
// Good - let DOM order determine tab order
<input />
<input />
<input />Remove from Tab Order
Use tabIndex={-1} for elements that should be focusable programmatically but not via Tab:
// Programmatically focusable, not in tab order
<div tabIndex={-1} ref={errorRef}>
{error}
</div>
// Later: errorRef.current?.focus()Keyboard Shortcuts
For custom shortcuts, use react-aria hooks:
import { useKeyboard } from "react-aria";
function SearchInput() {
const { keyboardProps } = useKeyboard({
onKeyDown: (e) => {
if (e.key === "Escape") {
clearSearch();
}
},
});
return <input {...keyboardProps} />;
}Rules
- Use
<button>for actions,<a>for navigation - Never use
divorspanwithonClickwithout keyboard support - Prefer react-aria-components for complex interactive widgets
- Don't use positive
tabIndexvalues - Use
tabIndex={-1}for programmatic focus targets - Ensure all interactive elements are reachable via Tab key
- Test keyboard navigation by unplugging your mouse