intermediate
Focus management
Move, trap, restore, and visibly style focus for dialogs, route changes, validation errors, and custom widgets.
Focus management moves keyboard attention intentionally: into a newly opened dialog, within a trap while modal, back to the trigger on close, and to validation errors after failed submit. Route changes in SPAs should move focus to main content or announce the new view.
function openDialog(trigger: HTMLElement, dialog: HTMLElement) {
lastFocus = trigger;
dialog.showModal();
dialog.querySelector<HTMLElement>('[data-autofocus]')?.focus();
}
function closeDialog() {
dialog.close();
lastFocus?.focus();
}
| Scenario | Pattern | |----------|---------| | Modal open | Focus first focusable or titled region | | Modal close | Restore focus to trigger | | Client route | Focus `<h1>` or `main` with `tabindex="-1"` | | Inline error | Focus first invalid field + `aria-invalid` |
Focus traps must still allow Escape and return focus—trapping without an exit path fails WCAG and real users.
On interviews: difference between autofocus and programmatic focus; `inert` vs focus trap libraries; focus after React Router navigation.
Common pitfalls: opening modal without moving focus; leaving focus on a removed node; trapping background scroll but not focus; multiple concurrent traps.
The trade-off is animation polish versus immediate keyboard context after UI changes.
Checklist:
- Save and restore trigger focus for overlays.
- Move focus on route and major state changes.
- Pair focus moves with live announcements when needed.
- Test tab cycle inside dialogs end to end.