ESC

Type to search the knowledge base.

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

  1. Visible focus — don’t remove outlines without a clear :focus-visible replacement.
  2. DOM order ≈ reading order — careful with CSS visual reordering.
  3. Mode changes move focus — dialog open, route change, menu open.
  4. 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>
  1. Remember document.activeElement
  2. Open UI; focus the dialog container or first focusable control
  3. Trap Tab / Shift+Tab inside (or use showModal())
  4. Escape closes
  5. Restore focus to the opener

Native <dialog> + showModal() handles a lot of this. Prefer it when you can.

SPA navigations

After client-side routing:

  1. Update document.title
  2. Move focus to main or the page h1 (tabindex="-1" if needed)
  3. 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();

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 guides