Keyboard Navigation, Focus Management, and Testing Accessibility
In Parts 1 and 2, we explored why ARIA matters and how to apply it to real-world React components.
In this final part, we focus on what truly separates accessible interfaces from merely well-labeled ones:
keyboard interaction, focus control, and verification.
Accessibility is not complete until it works without a mouse — and continues to work as your code evolves.
1. Keyboard Accessibility Is Not Optional
For many users, the keyboard is not a convenience.
It is the only input device.
If your React application cannot be fully operated using:
TabEnterSpace- Arrow keys
Escape
then it is not accessible — regardless of how much ARIA you add.
ARIA describes interaction. Keyboard support enables it.
2. Tab Order: Let the Browser Do the Work
Prefer natural tab order
<button>Save</button>
<a href="/profile">Profile</a>
<input />
Native elements already define correct focus behavior.
Avoid unnecessary tabIndex
❌ Bad:
<div tabIndex={0}>Item</div>
<div tabIndex={0}>Item</div>
<div tabIndex={0}>Item</div>
This creates noise and confusion.
Valid use cases for tabIndex
<div tabIndex={0} role="button">
Only when:
- No native element exists
- You fully implement keyboard behavior
3. Roving TabIndex: Advanced Keyboard Patterns
Used in:
- Menus
- Tabs
- Lists
- Toolbars
Concept
Only one item is focusable at a time.
tabIndex={isActive ? 0 : -1}
Keyboard arrows move focus programmatically.
This prevents users from tabbing through dozens of items unnecessarily.
Good keyboard UX is invisible — until it’s missing.
4. Focus Management in React
Focus is part of UI state
React manages visual state.
You must manage focus state explicitly.
Example: auto-focus on error
const inputRef = useRef(null);
useEffect(() => {
if (error) {
inputRef.current?.focus();
}
}, [error]);
This simple act can dramatically improve usability.
5. Modals: Focus Trapping and Restoration
Opening a modal
- Focus moves inside the modal
- First meaningful element receives focus
While open
- Focus stays inside
Tabcycles within modalEscapecloses modal
Closing a modal
- Focus returns to the triggering element
Failure in any step leads to user disorientation.
ARIA supports this — but cannot enforce it.
6. Live Regions: Announcing Dynamic Changes
Some UI updates do not involve focus changes:
- Toast messages
- Background validation
- Auto-save notifications
Solution: aria-live
<div aria-live="polite">
Changes saved successfully
</div>
Variants:
polite– announce when convenientassertive– interrupt immediately (use sparingly)
Do not overuse live regions. Silence is sometimes better.
7. Testing Accessibility in React
Accessibility is a regression risk.
1️⃣ Manual testing (essential)
- Navigate using keyboard only
- Disable mouse
- Try real screen readers:
- VoiceOver (macOS)
- NVDA (Windows)
No tool replaces human testing.
2️⃣ Automated testing (supporting role)
Use tools like:
eslint-plugin-jsx-a11yaxe-core@testing-library/jest-dom
expect(screen.getByRole('button')).toBeEnabled();
Automation catches:
- Missing roles
- Invalid ARIA
- Obvious violations
But it cannot judge usability.
8. Accessibility as a Team Practice
Mature teams treat accessibility as:
- A code review requirement
- A shared responsibility
- A non-negotiable standard
Questions to ask in reviews:
- Can I use this with a keyboard?
- Is focus handled intentionally?
- Does ARIA reflect real state?
- Are native elements used properly?
Accessibility is not owned by one person.
It is owned by the team.
9. Final Thoughts: Accessibility Is Engineering Integrity
ARIA is not about adding attributes.
It is about keeping promises.
Promises that:
- Components behave as they claim
- State is communicated honestly
- No user is silently excluded
Anyone can build a UI that works for themselves.
Professionals build UIs that work for others.
This is what it means to build interfaces that everyone can use.