Focus Management
Keyboard focus as real UI state — modals, SPAs, menus, and patterns from platform a11y guidance.
intermediate2 min read
- a11y
- focus
- keyboard
If keyboard focus order doesn’t match the story on screen, the product is broken for a large set of users. Focus is state. Treat it like state.
MDN and the ARIA Authoring Practices are the primary references; this page is the interview-and-implementation digest.
Principles
- Visible focus — don’t remove outlines without a clear
:focus-visiblereplacement. - DOM order ≈ reading order — careful with CSS visual reordering.
- Mode changes move focus — dialog open, route change, menu open.
- Return the trip — close the dialog, focus the control that opened it.
tabindex without regret
| Value | Meaning |
|---|---|
| omit | Default: natively focusable elements only |
0 |
Add custom widgets into tab order |
-1 |
Programmatic focus only (dialogs, SPA main) |
> 0 |
Don’t — you will invent a second tab order |
<div role="dialog" aria-modal="true" aria-labelledby="title" tabindex="-1">
<h2 id="title">Delete file?</h2>
…
</div>
Modal pattern (implement or use <dialog>)
- Remember
document.activeElement - Open UI; focus the dialog container or first focusable control
- Trap Tab / Shift+Tab inside (or use
showModal()) - Escape closes
- Restore focus to the opener
Native <dialog> + showModal() handles a lot of this. Prefer it when you can.
SPA navigations
After client-side routing:
- Update
document.title - Move focus to
mainor the pageh1(tabindex="-1"if needed) - Don’t leave focus on a footer link from the previous view
document.title = `${pageTitle} · Frontend Beauty`;
const main = document.getElementById('main-content');
main?.setAttribute('tabindex', '-1');
main?.focus();
Menus and comboboxes
Follow APG: arrows move highlight; Tab usually exits; typeahead for long lists. Pick either roving tabindex or aria-activedescendant and stick to it.
How to test (non-negotiable)
- Unplug the mouse: Tab, Shift+Tab, Enter, Space, Escape, arrows
- Screen reader smoke test on critical flows
- Automated axe checks catch missing names; they don’t catch bad focus order
Further reading
Related
Related guides
- WCAG Principles: POURPerceivable, Operable, Understandable, Robust — the four WCAG principles with concrete frontend examples, levels, and how to use them in reviews.
- Accessible Combobox PatternBuild or evaluate comboboxes with APG keyboard behavior, aria-expanded/activedescendant, and filterable listbox pairing.
- Accessible Forms ErrorsLabel inputs, associate errors with fields, announce failures, and avoid placeholder-only forms that break accessibility.
- Accessible Menus PatternAPG menu and menubar keyboard model — arrow navigation, escape to close, aria-expanded, and when a disclosure is enough.
- Accessible Modals PatternsModal dialogs that work: focus trap, Escape, return focus, aria-modal, and why native dialog or APG patterns beat div soup.