AI Builds Interfaces That Require a Mouse
AI generates interfaces with: custom interactive elements that are not focusable (div-based buttons, span-based links — not in the tab order), no visible focus indicator (outline: none on everything — keyboard users cannot see where they are), illogical tab order (focus jumps from header to footer to sidebar — does not follow visual flow), no focus management (opening a modal does not move focus to it, closing does not return focus to the trigger), and no skip navigation (keyboard users must tab through 50 navigation links to reach the main content). 15% of users rely on keyboard navigation: motor disabilities, power users, screen reader users, and anyone with a broken mouse.
Modern keyboard navigation is: logically ordered (tab order follows visual reading flow — left to right, top to bottom), focus-managed (modals trap focus, SPA navigation moves focus to new content), roving-indexed (composite widgets like tabs and menus use arrow keys internally), skip-equipped (skip-to-content link as the first focusable element), and visibly focused (:focus-visible provides a clear indicator for keyboard users without affecting mouse users). AI generates none of these.
These rules cover: logical tab order, focus management for modals and SPAs, roving tabindex for composite widgets, skip-to-content links, keyboard shortcut design, and :focus-visible styling.
Rule 1: Logical Tab Order
The rule: 'Tab order must follow the visual reading flow. The default tab order follows DOM order — which should match the visual layout. Do not use positive tabIndex values (tabIndex="5") to reorder — they create unpredictable tab sequences that break when elements are added or removed. Use tabIndex="0" to add non-native elements to the tab order (only when a native element cannot be used). Use tabIndex="-1" to make elements programmatically focusable but not in the tab order (for focus management targets).'
For DOM order alignment: 'If the visual layout shows: header, then sidebar, then main content — the DOM should be: header, sidebar, main content. CSS can visually reposition elements (grid order, flexbox order, absolute positioning) but the tab order follows the DOM, not the visual layout. If CSS reordering causes the tab order to differ from the visual order: restructure the DOM to match. Test: press Tab repeatedly and verify focus moves through the page in the order a user would expect by reading left-to-right, top-to-bottom.'
AI generates: tabIndex="3" on a sidebar button, tabIndex="1" on a header link, tabIndex="2" on a form input — manual tab ordering that breaks when any element is added. Or: elements visually in one order but in a different DOM order (focus jumps unpredictably). Logical tab order: DOM matches visual flow, no positive tabIndex values, and a Tab-through test catches order issues in seconds.
- Tab order follows DOM order — DOM must match visual reading flow
- Never positive tabIndex values (1, 2, 3) — unpredictable, fragile, breaks on any DOM change
- tabIndex='0': add to tab order (non-native elements only when necessary)
- tabIndex='-1': programmatically focusable but not in tab order (focus management targets)
- CSS reordering does not change tab order — restructure DOM if visual and tab order diverge
Rule 2: Focus Management for Modals and SPAs
The rule: 'When UI context changes, manage focus explicitly. Modal opens: move focus to the first focusable element inside the modal (or the modal container with tabIndex="-1"). Modal closes: return focus to the element that triggered the modal. SPA route change: move focus to the main content heading (the <h1> of the new page). Toast notification: do not steal focus (use aria-live instead). Dropdown opens: move focus to the first option. Dropdown closes: return focus to the trigger button.'
For focus trapping in modals: 'When a modal is open, Tab must cycle within the modal — not escape to the page behind it. Implementation: listen for Tab keydown, if focus is on the last focusable element in the modal and Tab is pressed, move focus to the first focusable element (wrap around). If Shift+Tab is pressed on the first element, move to the last. The inert attribute on the background content is the modern solution: <main inert> makes all background content non-focusable and non-interactive while the modal is open.'
AI generates: a modal that opens with no focus change (focus stays on the trigger behind the modal), Tab escapes the modal to the background page (the user is navigating the invisible background), and closing the modal leaves focus at the end of the document (the user must Tab back to where they were). Three focus management failures in one modal. Managed focus: open moves to modal, Tab is trapped, close returns to trigger. Three fixes, seamless modal experience for keyboard users.
AI modal: focus stays on trigger behind the modal, Tab escapes to background, close leaves focus at document end. Three failures. Managed: open moves focus to modal, Tab is trapped inside, close returns to trigger. Three fixes make the modal seamless for keyboard users.
Rule 3: Roving Tabindex for Composite Widgets
The rule: 'Composite widgets (tabs, menus, toolbars, radio groups) use roving tabindex: only one item in the group is in the tab order (tabIndex="0"), all others are tabIndex="-1". Arrow keys move focus between items (updating which item has tabIndex="0"). Tab moves focus OUT of the group to the next component. This pattern: one Tab stop for the entire group (not 10 Tab stops for 10 tabs), arrow keys for internal navigation (familiar from native UI), and Home/End for first/last item.'
For a tab widget: 'Tab list has 5 tabs. Only the active tab has tabIndex="0". The other 4 have tabIndex="-1". User Tabs into the tab list: focus lands on the active tab. Arrow right: focus moves to the next tab (tabIndex swaps). Arrow left: focus moves to the previous tab. Home: focus moves to the first tab. End: focus moves to the last tab. Tab (not arrow): focus leaves the tab list and moves to the tab panel content. This matches WAI-ARIA Authoring Practices for the tab pattern.'
AI generates: 5 tabs, all with tabIndex="0". The user must Tab 5 times to get through the tab list to the content. Or: all tabs unfocusable (tabIndex not set, cannot reach tabs by keyboard). Roving tabindex: one Tab stop for the group, arrow keys inside, Tab to leave. The keyboard experience matches how native desktop tab widgets work — familiar and efficient.
- One Tab stop per composite widget — arrow keys for internal navigation
- Active item: tabIndex='0'. Inactive items: tabIndex='-1'
- Arrow keys move focus and swap tabIndex values within the group
- Home/End: jump to first/last item in the group
- Tab: exit the group to the next component — not move within the group
5 tabs, all tabIndex='0': user must Tab 5 times to get through the tab list. Roving tabindex: one Tab stop for the group, arrow keys to navigate tabs, Tab to exit to the content. Keyboard experience matches native desktop widgets — familiar and efficient.
Rule 4: Skip-to-Content Links
The rule: 'The first focusable element on every page is a skip-to-content link: <a href="#main" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-white focus:px-4 focus:py-2">Skip to content</a>. Visually hidden by default (sr-only). Visible when focused (focus:not-sr-only). On activation: focus moves to <main id="main" tabIndex="-1">. The skip link lets keyboard users bypass the navigation and jump directly to the main content — essential on pages with 20+ navigation links.'
For multiple skip links: 'On complex pages, provide multiple skip links: Skip to content (#main), Skip to search (#search), Skip to navigation (#nav). These appear as a list when the user first Tabs into the page. Each link targets a landmark region with tabIndex="-1" (to receive focus programmatically). The user presses Tab once: skip links appear. They choose their destination. One more Tab: the page navigation appears (for users who want to navigate normally). Two paths: skip to content or navigate normally.'
AI generates: no skip link. The first focusable element is the logo link, followed by 15 navigation links, then the main content. A keyboard user presses Tab 16 times to reach the first article heading. On every page. With a skip link: Tab once, Enter, focus is on the main content. 16 keystrokes reduced to 2. The skip link is the single most impactful keyboard accessibility improvement for content-heavy sites.
No skip link: Tab 16 times through navigation to reach the article. On every page. Skip-to-content link: Tab once, Enter, focus on main content. 16 keystrokes reduced to 2. The single most impactful keyboard accessibility improvement for content-heavy sites.
Rule 5: :focus-visible Styling
The rule: 'Use :focus-visible instead of :focus for focus indicators. :focus fires on every focus event (keyboard Tab AND mouse click). :focus-visible fires only on keyboard focus (Tab, not click). This means: keyboard users see the focus ring (they need it for navigation), mouse users do not see the focus ring (they do not need it — they can see where they clicked). Style: :focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }. Never: outline: none or *:focus { outline: 0 } — these remove the focus indicator for keyboard users.'
For custom focus indicators: 'The default browser focus ring is functional but may clash with your design. Custom: :focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; border-radius: 4px; }. Requirements: (1) 3:1 contrast ratio against adjacent colors (WCAG 2.2). (2) At least 2px thick (visible at any size). (3) Offset from the element edge (outline-offset: 2px prevents overlap with element borders). (4) Consistent across all interactive elements (same style for buttons, links, inputs — user recognizes the indicator pattern).'
AI generates: *:focus { outline: none; } or button:focus { outline: 0; box-shadow: none; } — focus indicator removed entirely. Keyboard users: cannot see where they are on the page. They Tab blindly, hoping they are on the right element. With :focus-visible: keyboard users see a clear blue ring on the focused element. Mouse users see no ring (clean visual). Both user groups get the experience they need. One CSS selector serves both.