Focus management is the keyboard user's cursor
For someone using a mouse, the pointer is where their attention is. For someone using a keyboard or a screen reader, focus is that pointer — it is where they are on the page and where their next keystroke will land. Once you internalise “focus is the cursor,” a whole category of accessibility bugs becomes obvious: a dialog that opens but does not move focus is a cursor that did not follow the click; a menu you cannot Tab out of is a cursor stuck in a corner; a route change that leaves focus on the old link is a cursor teleported to nowhere. Managing focus deliberately is not a nicety — it is keeping the cursor where the user expects it.
Move focus when the context changes
When you open something new — a dialog, a drawer, a menu — move focus into it, or the keyboard user is left “behind” it, tabbing through the page underneath. The same applies to a single-page-app route change: the browser normally focuses the new document, but a client-side navigation does not, so the user’s cursor stays on the link they clicked while the whole page changes around it. Move it deliberately:
// on route change, move focus to the new page's heading so the cursor "arrives"
useEffect(() => {
document.getElementById("page-heading")?.focus();
}, [pathname]);
Trap focus while a modal is open — then release it
While a modal is open, Tab should cycle within it, not escape to the page behind (which is inert to the eye but still reachable by keyboard). Trap focus on open, and — the half people forget — restore it to the trigger on close:
function useFocusTrap(ref, open) {
const opener = useRef(null);
useEffect(() => {
if (!open) return;
opener.current = document.activeElement; // remember the trigger
const focusables = ref.current.querySelectorAll("button, [href], input, [tabindex]");
focusables[0]?.focus(); // focus in
return () => opener.current?.focus(); // focus back on close
}, [open]);
}
A trap without the restore is its own bug: the user closes the dialog and their cursor is nowhere.
Never destroy focus, and keep it visible
Two failure modes finish the picture. First, do not remove or hide the focused
element without moving focus somewhere sensible first — deleting the focused row of
a list should move focus to the next row, not drop it to <body>, which yanks the
cursor to the top of the page. Second, never do outline: none without a
replacement: the focus ring is the visible cursor, and hiding it blinds keyboard
users to their own position. Use :focus-visible so the ring shows for keyboard
users without cluttering mouse clicks. Treat focus as the cursor it is — move it on
context changes, trap and restore it in overlays, never destroy it, and always keep
it visible — and your app becomes operable for people who never touch a mouse. The
accessible-combobox and tabs-molecule exercises are built around exactly this
focus choreography, which is where the “focus is the cursor” idea stops being a
slogan and becomes code.