Keyboard Navigation: Making Your Website Usable Without a Mouse
Introduction
Keyboard navigation is the ability to use a website with only a keyboard—no mouse, touchpad, or touchscreen required. This isn’t just about convenience; it’s a fundamental accessibility requirement. WCAG 2.1.1 mandates that all functionality must be available from a keyboard, making this one of the most critical accessibility criteria.
Millions of users rely on keyboard navigation: people with motor disabilities who can’t use a mouse, blind users who navigate with screen readers, power users who prefer keyboard shortcuts, and anyone with a temporary injury affecting mouse use.
Who Needs Keyboard Navigation
Primary Users
- Motor disability users - May use alternative input devices that emulate keyboards
- Blind users - Screen readers rely on keyboard navigation
- Low vision users - Often combine keyboard with screen magnification
- Repetitive strain injury - Keyboard may be less painful than mouse
- Power users - Prefer keyboard shortcuts for efficiency
| Device |
Function |
Emulates |
| Switch device |
Single or multiple switches for input |
Keyboard keys |
| Sip-and-puff |
Breath-controlled input |
Keyboard/mouse |
| Eye tracking |
Gaze-based selection |
Mouse/keyboard |
| Voice control |
Speech commands |
Keyboard/mouse |
| Head pointer |
Head movement tracking |
Mouse |
All these devices ultimately work through keyboard interaction, making keyboard accessibility the foundation for broader device support.
Essential Keyboard Operations
Standard Navigation Keys
| Key |
Function |
| Tab |
Move to next focusable element |
| Shift + Tab |
Move to previous focusable element |
| Enter |
Activate links and buttons |
| Space |
Activate buttons, toggle checkboxes |
| Arrow keys |
Navigate within components (menus, tabs, grids) |
| Escape |
Close dialogs, cancel operations |
| Home/End |
Jump to first/last item in lists |
WCAG Keyboard Requirements
| Criterion |
Level |
Requirement |
| 2.1.1 Keyboard |
A |
All functionality accessible via keyboard |
| 2.1.2 No Keyboard Trap |
A |
Focus can always move away from any element |
| 2.1.4 Character Key Shortcuts |
A |
Single-key shortcuts can be turned off/remapped |
| 2.4.3 Focus Order |
A |
Focus sequence must be logical |
Common Keyboard Accessibility Problems
1. Non-Focusable Interactive Elements
<div onclick="submitForm()">Submit</div>
<button type="submit">Submit</button>
<div role="button" tabindex="0" onclick="submitForm()" onkeydown="handleKey(event)">
Submit
</div>
2. Keyboard Traps
modal.addEventListener('keydown', (e) => {
e.preventDefault();
});
modal.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
if (e.key === 'Tab') handleFocusTrap(e);
});
3. Missing Skip Links
<nav>
</nav>
<main>Content here</main>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>
</nav>
<main id="main-content">Content here</main>
4. Illogical Focus Order
<div style="display: flex; flex-direction: row-reverse;">
<button>Third visually, first in DOM</button>
<button>Second visually and in DOM</button>
<button>First visually, third in DOM</button>
</div>
5. Hover-Only Interactions
.dropdown-menu {
display: none;
}
.dropdown:hover .dropdown-menu {
display: block;
}
.dropdown:hover .dropdown-menu,
.dropdown:focus-within .dropdown-menu {
display: block;
}
Testing Keyboard Navigation
Quick Manual Test
- Unplug your mouse or put it aside
- Press Tab - Does focus move through all interactive elements?
- Check focus visibility - Can you see where you are?
- Test activation - Do Enter and Space work correctly?
- Look for traps - Can you always Tab away from any element?
- Check modals - Can you close them with Escape?
What to Check on Every Page
- [ ] All links can be reached and activated
- [ ] All buttons can be reached and activated
- [ ] All form fields can be filled and submitted
- [ ] All menus/dropdowns can be opened and navigated
- [ ] All modals can be opened and closed
- [ ] Focus order matches visual/reading order
- [ ] No keyboard traps exist
- [ ] Skip links are available
Common Problem Areas
- Custom dropdowns - Often missing keyboard support
- Carousels/sliders - May trap focus or skip controls
- Modal dialogs - Need focus trapping and Escape to close
- Mega menus - Complex navigation patterns
- Video players - Custom controls often inaccessible
- Canvas elements - Need keyboard alternatives
Native vs Custom Elements
Use Native Elements When Possible
| Native Element |
Custom Alternative |
Keyboard Built-in |
<button> |
<div role="button"> |
✅ Native has it |
<a href> |
<span onclick> |
✅ Native has it |
<select> |
Custom dropdown |
✅ Native has it |
<input type="checkbox"> |
Custom toggle |
✅ Native has it |
<details> |
Custom accordion |
✅ Native has it |
When Custom Components Are Needed
If you must use custom components, implement:
- tabindex=“0” - Makes element focusable
- Role - Tells screen readers what it is
- Keyboard handlers - Enter, Space, Arrow keys as appropriate
- Focus management - Move focus when needed
Quick Fixes
Making a Div Clickable and Keyboard Accessible
<div onclick="doAction()">Click me</div>
<div
role="button"
tabindex="0"
onclick="doAction()"
onkeydown="if(event.key==='Enter'||event.key===' ')doAction()"
>
Click me
</div>
<button onclick="doAction()">Click me</button>
Adding a Skip Link
<a href="#main" class="skip-link">Skip to main content</a>
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px;
background: #000;
color: #fff;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
</style>
Best Practices Summary
| Do |
Don’t |
| Use native HTML elements |
Build custom controls unnecessarily |
| Test without a mouse |
Assume mouse testing covers keyboard |
| Provide skip links |
Force users through entire navigation |
| Make focus visible |
Remove focus indicators |
| Use logical tab order |
Use positive tabindex values |
| Support Enter and Space |
Require hover for functionality |
Related Articles
References
- W3C - WCAG 2.2 SC 2.1.1 Keyboard
- W3C - WCAG 2.2 SC 2.1.2 No Keyboard Trap
- WebAIM - Keyboard Accessibility
- MDN - Keyboard-navigable JavaScript widgets