Keyboard Navigation
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints field explicitly states automated=no, and tools listed (axe, wave, nvda, keyboard-testing) require manual keyboard walkthroughs or assistive technology testing. Automated tools like axe can catch some issues (e.g., missing tabindex) but cannot simulate full keyboard interaction flows like focus trapping in modals or arrow-key navigation in custom widgets — these require hands-on testing.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix is framed as testing the entire application, and common_mistakes span multiple independent issues: removing outline:none, fixing tabindex ordering, adding keydown handlers to non-button elements, and managing modal focus. Each fix may be a small change, but the pattern of misuse tends to be spread across many components and templates throughout a frontend codebase, making remediation a multi-file effort.
Closest to 'persistent productivity tax' (b5). Applies broadly across all web UI contexts. Every interactive component — buttons, dropdowns, modals, custom widgets — must be independently verified and maintained for keyboard accessibility. Any new component added by any developer carries the tax of needing correct focus management and event handling, persistently slowing frontend work streams without being fully architectural.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field states that keyboard navigation is perceived as only needed for users without mice, leading developers to deprioritize it. Additionally, common mistakes like outline:none and positive tabindex are well-documented gotchas that most frontend developers encounter and eventually learn, but the 'obvious' approaches (removing the ugly focus ring, manually ordering tabs) are routinely wrong in ways that aren't immediately apparent.
Also Known As
TL;DR
Explanation
Keyboard navigation relies on tab order (set by DOM order, modified by tabindex), focus indicators (visible outline on focused elements), keyboard shortcuts (Enter/Space to activate buttons, Escape to close modals), and no keyboard traps (user can always navigate away). Focus management is critical in SPAs — when a modal opens, focus should move to it; when it closes, focus should return to the trigger. ARIA roles ensure screen readers announce the correct element type.
Diagram
flowchart TD
USER[Keyboard user] --> TAB[Tab key]
TAB --> FOCUS{Element focusable?}
FOCUS -->|yes| INDICATOR[Focus indicator visible<br/>outline ring]
FOCUS -->|no - div span| BROKEN[Inaccessible<br/>cannot reach element]
INDICATOR --> INTERACT{Element type}
INTERACT -->|button| ENTER_SPACE[Enter or Space activates]
INTERACT -->|link| ENTER2[Enter follows link]
INTERACT -->|input| TYPE[Type text]
INTERACT -->|select| ARROWS[Arrow keys choose option]
BROKEN -.->|fix with| TABINDEX[tabindex=0<br/>+ keyboard handler]
style BROKEN fill:#f85149,color:#fff
style INDICATOR fill:#238636,color:#fff
style TABINDEX fill:#d29922,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- outline: none on :focus — removes the only visual indicator of keyboard focus; use outline: none only with a custom focus style.
- tabindex > 0 — positive tabindex overrides natural DOM order, creating confusing navigation for keyboard users.
- Click-only event handlers on non-button elements — divs with onclick are not keyboard-activatable without tabindex and keydown handling.
- Not managing focus in modals — focus must move to the modal and be trapped within it until closed.
Code Examples
/* Removing focus indicator — keyboard users cannot see where they are: */
* { outline: none; } /* Never do this */
/* Non-keyboard-accessible interactive element: */
<div onclick="openMenu()" style="cursor:pointer">Menu</div>
<!-- Not in tab order, no keyboard activation -->
/* Custom focus styles instead of removing outline: */
:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
<!-- Use native interactive elements: -->
<button type="button" onclick="openMenu()" aria-expanded="false">Menu</button>
<!-- Natively: tab-focusable, Enter/Space activates, ARIA state communicated -->