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
- Accessible Menus PatternAccessible Menus Pattern explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Accessible Combobox PatternAccessible Combobox Pattern explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Color Contrast RequirementsColor Contrast Requirements explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Do Not Rely on Color AloneDo Not Rely on Color Alone explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Reduced Motion PreferencesReduced Motion Preferences explained for frontend engineers — mental model, examples, common mistakes, and interview tips.