R3.10.3focus successordesign

When a focused element is removed, a successor must take focus

Aliases: focus lost · activeElement removed · successor focus · unmount focus

What it is

Focus is a pointer to one node in the document. When that node is deleted, unmounted, filtered out of a list, or removed from the tree after submit, the pointer has nowhere to point. Engines typically drop focus to body, or into a state with no active element. The next keystroke restarts from the top of the page; a screen reader announces the document title; the user no longer knows where the task left off. A focus successor is the next focusable node chosen before removal: a neighbour, a parent, a nearby primary action, ready to catch the pointer.

This is the moment the node holding focus is destroyed — not restoring focus to the trigger after an overlay closes, and not resetting focus to main content after a route change.

Why it happens

document.activeElement must reference a node still connected to the document. Removal (remove(), framework unmount, virtual-list recycling, display: none making it unfocusable) invalidates that reference. Specs do not require engines to move to a "logical next item"; the common fallback is body, and some mobile WebViews briefly have no focus at all. The next Tab then starts at the first focusable node in the document; arrow keys may do nothing. Screen readers follow focus and speak the jump as a new page position.

The successor must be chosen before removal: compute the next node (next or previous item in a list; an "Add" action or the list container if empty), focus() it, then delete. Delete-then-search means activeElement is already body, and a later move is a noticeable flash that the screen reader speaks twice. The successor itself must be focusable; a tabindex="-1" container can take programmatic focus, but you cannot assume the user can Tab to a decorative wrapper that was never in the tab order.

Where it stops holding

If focus is not on the node being removed, do not move it — stealing the control someone else is using is a worse interruption. Auto-refreshing tables that rerender the current row every second must not keep shoving focus to "the next item", or the keyboard cannot rest. Infinite-scroll recycling of offscreen rows still owes a successor if focus sits on the row being recycled; "it is still in the same dataset" does not keep focus alive. Native <dialog> close and route-level unmounts are a different handoff (back to the trigger, or to the new page's entry) and must not share the "deleted the current list item" heuristic. Custom carets inside rich text or canvas do not use activeElement and need their own caret successor.

Applying it

  • In any operation that will destroy the focused node (delete, vanish-after-submit, filter out the current row, unmount a wizard step), assign a successor and focus() it before changing the DOM.
  • List deletion: next item, else previous, else the empty-state action or the list container (tabindex="-1"). After a control disappears on successful submit: move to the result heading or the next task, not body.
  • Do not steal focus from unrelated nodes. Do not delete first and patch later.
  • How to check: put focus on the control that will vanish, perform the removal, and immediately confirm document.activeElement is the chosen successor and that it is visible. Press Tab once and confirm you do not land on the site logo. With a screen reader, the move should be spoken once, as the successor, not as the whole page.

Related

  • Same group: R3.10.1 Focus inside an overlay must stay within its interactive range · R3.10.2 Focus visibility must not depend on whether a mouse is in use
  • Nearby: R3.02 Focus Management · J2.06 Focus Visibility
  • Search terms: focus successor · activeElement · focus lost · removed focused node

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/R3.10.3