Focus Handoff in Master-Detail Layouts
Permalink to "Focus Handoff in Master-Detail Layouts"Master-detail layouts — an email client’s message list and reading pane, a CRM’s lead list and lead record, an admin table with a side inspector — put two regions side by side. The list chooses a record; the detail shows it. The question every implementation has to answer is what happens to keyboard focus when the user chooses a record. Leave focus in the list and screen reader users never hear the detail. Move focus into the detail every time and users browsing the list are yanked away on each arrow press.
This guide gives the rules. It belongs to master-detail & split views.
Spec reference
Permalink to "Spec reference"- SC 2.4.3 Focus Order (A) — focus must move in a way that preserves meaning and operability; moving focus to the detail on explicit activation does, moving it on every arrow press does not.
- SC 3.2.1 On Focus (A) — receiving focus must not trigger a change of context. Updating a detail pane as the list’s selection follows focus is acceptable (content change); moving focus out of the list is a change of context.
- SC 3.2.2 On Input (A) — similar for selection changes made with input.
- SC 2.4.7 Focus Visible (AA) — the row the user came from should remain visibly selected while they read the detail.
- SC 4.1.3 Status Messages (AA) — if the detail updates without focus moving, consider a brief status message.
When to use — and when not to
Permalink to "When to use — and when not to"Move focus to the detail pane when the user activates a record — Enter, double-click, or an explicit “Open” button. That signals intent to read or edit the record. Keep focus in the list when the user is only selecting — arrow keys, single click in a list with follow-focus selection — so they can keep browsing.
Do not move focus to the detail on page load just because the first record is preselected. Users arriving at the page expect to start at the top.
The misapplication: “selection follows focus” in the list combined with “focus follows selection” into the detail. Every arrow press moves focus out of the list, making it impossible to browse. A close second is the opposite: Enter opens the record but focus stays on the list row, so a screen reader user hears nothing and may not realise anything opened.
Annotated code example
Permalink to "Annotated code example"// List: arrows select; Enter activates (ARIA listbox or grid in the list)
list.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
const row = moveSelection(e.key === 'ArrowDown' ? 1 : -1); // roving focus within list
showDetail(row.dataset.id, { focus: false }); // content change only (SC 3.2.1)
}
if (e.key === 'Enter') {
e.preventDefault();
lastRowId = activeRow().dataset.id;
showDetail(lastRowId, { focus: true }); // explicit activation (SC 2.4.3)
}
});
function showDetail(id, { focus }) {
renderDetail(id);
if (focus) document.getElementById('detail-heading').focus(); // h2 with tabindex="-1"
}
// Detail → list: Escape returns to the row the user came from
detail.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !e.defaultPrevented) {
list.querySelector(`[data-id="${lastRowId}"]`)?.focus();
}
});
<section aria-labelledby="detail-heading" class="detail-pane">
<!-- Focus target: the heading names what was opened -->
<h2 id="detail-heading" tabindex="-1">Lund & Co — Invoice INV-1042</h2>
…
</section>
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | Context | Expected announcement | AT deviations |
|---|---|---|---|
| Down arrow | List | Next row; detail updates silently | — |
| Enter | List | “Lund & Co — Invoice INV-1042, heading level 2” | JAWS may continue reading the pane |
| Escape | Detail | Row in list: “INV-1042, Lund & Co, selected” | Escape inside an open combobox closes it first |
| Tab past the end of detail | Detail | Next region or page footer | — |
Integration context
Permalink to "Integration context"The list half of this pattern is usually a listbox or grid; its focus mechanism is roving tabindex. When the detail updates without focus, see announcing detail panel updates. Moving between the panes with F6 is covered in keyboard navigation between list and detail panes.
Gotchas
Permalink to "Gotchas"Escape conflicts. Escape also closes menus, comboboxes and dialogs inside the detail. Only treat Escape as “back to list” if nothing inside handled it — check e.defaultPrevented or listen in the bubbling phase.
List re-render while reading. If the list refreshes while focus is in the detail, the row the user came from may be rebuilt. Store its ID, not the element, and look it up when returning.
Narrow screens. On small viewports the panes stack or the detail replaces the list. Then activation is navigation — focus the detail heading as on a new page, and Back returns to the list with the row focused.
Framework notes
Permalink to "Framework notes"In React, keep lastRowId in state and use a ref callback map to focus rows on return; avoid autoFocus on the detail heading, which fires on every render. In Angular, cdkFocusInitial is for dialogs, not panes — call focus() on a ViewChild heading after the detail renders (afterNextRender). In Vue, nextTick then focus the heading ref.
useEffect(() => {
if (openedBy === 'activation') headingRef.current?.focus(); // only on explicit activation
}, [recordId, openedBy]);
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Provide a MasterDetail layout component with an explicit onActivate versus onSelect API, a focus target slot for the detail heading and a built-in return key. Encoding the distinction in the API stops teams from wiring focus to selection by accident.
FAQ
Permalink to "FAQ"Should a single click in the list move focus to the detail?
Usually not. A click selects; double-click or an explicit Open button activates. Mouse users do not lose anything, and keyboard and screen reader users get a consistent model.
Why focus the heading rather than the first field?
The heading tells users what opened. Focusing the first field skips that context and, for forms, can put users straight into edit mode.
What if the detail pane is a dialog?
Then the dialog pattern applies: focus moves into it, Tab is contained, Escape closes it and focus returns to the row. See record inspector panels for the trade-off.
Should Escape be the only way back?
No. Also provide a visible “Back to list” button for pointer users and users who do not know the shortcut, and support F6 to move between panes.
Related
Permalink to "Related"- Announcing detail panel updates — when focus stays in the list
- Inspector panels: dialog or region? — choosing the detail container
- Restoring focus after closing complex modals — the dialog variant