Keyboard navigation works for all interactive elements; focus order is logical and visible
Why it matters
Keyboard-only users—including people with motor impairments, power users, and anyone using assistive switches—cannot interact with any element unreachable by Tab or that traps focus without escape. WCAG 2.2 SC 2.1.1 (Level A) and SC 2.4.3 (Level A) are both at stake, as is Section 508 2018 Refresh 503.3.3. A modal that swallows keyboard focus indefinitely, or a custom widget that responds only to mouse clicks, locks these users out entirely. Federal procurement standards mandate full keyboard operability, and failure here triggers compliance findings.
Severity rationale
Critical because broken keyboard access completely prevents interaction for users who rely solely on keyboards or assistive input devices, violating WCAG 2.2 SC 2.1.1 at Level A.
Remediation
Use semantic HTML (<button>, <a>, <input>) which receives keyboard focus by default. For custom widgets, add tabindex="0". Always preserve visible focus indicators—never suppress outline without a replacement:
button:focus-visible, a:focus-visible, input:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
For modal dialogs, trap focus inside while open and restore it to the trigger on close. In src/components/Modal.tsx, implement a focus-trap loop:
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstFocusable) {
e.preventDefault(); lastFocusable.focus();
} else if (!e.shiftKey && document.activeElement === lastFocusable) {
e.preventDefault(); firstFocusable.focus();
}
}
if (e.key === 'Escape') closeModal();
});
Detection
- ID:
keyboard-navigation - Severity:
critical - What to look for: Count all relevant instances and enumerate each. Before evaluating, extract and quote any relevant configuration or UI text found. Test keyboard navigation by tabbing through all interactive elements: buttons, links, form inputs, dropdowns, modals. Check for focus indicators (visible outline or highlight). Check that focus order follows the visual reading order (left-to-right, top-to-bottom). Look for keyboard traps (pressing Tab does not escape an element). Check for tab skip patterns in code (negative tabindex values that remove focusability).
- Pass criteria: Every interactive element is reachable via Tab key. At least 1 implementation must be verified. Tab order is logical (follows visual flow). Every focusable element has a visible focus indicator (not removed with
outline: nonewithout a replacement style). No keyboard traps exist. A partial or placeholder implementation does not count as pass. Report the count even on pass. - Fail criteria: Any interactive element is unreachable via keyboard, or focus order is illogical, or focus indicators are hidden without replacement, or keyboard traps exist.
- Skip (N/A) when: Never — keyboard navigation is fundamental to accessibility.
- Detail on fail: Name the problematic elements. Example:
"Modal dialog does not trap focus; Tab moves focus outside the modal when last button is tabbed. Close button has outline: none with no replacement focus style, making it impossible to see where focus is on the button." - Remediation: Use semantic HTML (
<button>,<a>,<input>) which are keyboard-accessible by default. For custom interactive elements, addtabindex="0"to make them focusable. Always show focus indicators:Never remove focus outlines without a replacement style. For modal dialogs, trap focus by moving focus to the first focusable element when opened and preventing Tab from moving focus outside:button:focus, a:focus, input:focus { outline: 2px solid #0066cc; outline-offset: 2px; }const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; modal.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } else if (!e.shiftKey && document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } });
External references
- wcag:2.2 · 2.1.1 — Keyboard
- wcag:2.2 · 2.4.3 — Focus Order
- section-508:2018-refresh · 503.3.3 — Keyboard Operability
Taxons
History
- 2026-04-18·v1.0.0·Initial import from gov-section-508·automated