Master-Detail & Split Views

Permalink to "Master-Detail & Split Views"

Master-detail is the layout of almost every serious data application: a list or table of records on one side, the selected record’s details on the other. Mail clients, CRMs, issue trackers, admin consoles, file managers and log viewers all use it. It is efficient for sighted mouse users because both halves are visible and one click links them. For keyboard and screen reader users, the same layout raises a series of questions the visual design never has to answer: when does focus move from the list to the detail? What do users hear when the detail changes but focus does not? How do they get from one pane to the other without tabbing through everything in between? Is the slide-in inspector a dialog or a region? And how do they resize the panes when the table needs more room?

The users most affected are screen reader users, who cannot see the detail update and need a model of where focus is; keyboard users, who need fast movement between panes; magnifier users, who often need one pane collapsed so the other fits at their zoom level; and users with cognitive disabilities, who are disoriented when focus jumps unexpectedly. The failure mode this topic prevents is a split view that is fast with a mouse and a maze without one.

This topic belongs to accessible data tables & grid systems.

WCAG criteria in scope

Permalink to "WCAG criteria in scope"
Criterion Level Relevance to master-detail layouts
1.3.1 Info and Relationships A Panes as named regions; detail summaries tied to rows
2.1.1 Keyboard A Activation, pane movement and splitter all keyboard-operable
2.1.2 No Keyboard Trap A Non-modal panels never trap focus
2.4.1 Bypass Blocks A F6, skip links and landmarks bypass the long list
2.4.3 Focus Order A Focus moves on activation and returns to the same row
2.5.7 Dragging Movements AA Splitter has a non-drag alternative
3.2.1 On Focus A Moving through the list does not move focus elsewhere
4.1.3 Status Messages AA Optional summaries and collapse messages

Prerequisites

Permalink to "Prerequisites"
Anatomy of an accessible split view Layers: named panes as regions at the base; the list with selection and activation; the detail container as region or dialog; pane movement with F6 and skip links; and the splitter for resizing. Anatomy of an accessible split viewNamed panesregions labelled "Invoices" and "INV-1042 details"List behaviourarrows select; Enter activates; Escape returnsDetail containerregion, non-modal dialog or modal dialogPane movementF6 cycling, skip links, last-focus memorySplitterrole="separator", arrow keys, Enter to collapse
Each concern maps to one guide in this topic.

ARIA & HTML spec reference

Permalink to "ARIA & HTML spec reference"
Element / attribute Valid values When to apply Common misuse
section aria-labelledby Heading ID Each pane Unnamed div panes
aside / complementary — Persistent secondary pane Several unnamed aside elements
dialog / showModal() — Blocking inspectors Custom traps on non-blocking drawers
role="dialog" (non-modal) — Transient, non-blocking inspectors Adding aria-modal="true"
h2 tabindex="-1" — Focus target on activation Focusing the pane container
aria-describedby on rows ID of summary Key detail fields in the list Duplicating the row text
role="separator" + tabindex="0" — Resizable divider Drag-only divs
aria-valuenow/min/max/text Numbers / text On the splitter Pixel position instead of pane size

Step-by-step implementation

Permalink to "Step-by-step implementation"

Step 1 — Selection versus activation (SC 3.2.1)

Permalink to "Step 1 — Selection versus activation (SC 3.2.1)"

Arrows change selection and update the detail silently; Enter activates. See focus handoff in master-detail layouts.

if (e.key.startsWith('Arrow')) showDetail(id, { focus: false });
if (e.key === 'Enter') showDetail(id, { focus: true });

Step 2 — Focus targets and return (SC 2.4.3)

Permalink to "Step 2 — Focus targets and return (SC 2.4.3)"

Focus the detail heading on activation; Escape returns to the same row by ID.

detailHeading.focus(); /* … */ list.querySelector(`[data-id="${lastId}"]`).focus();

Step 3 — Summaries on demand (SC 1.3.1)

Permalink to "Step 3 — Summaries on demand (SC 1.3.1)"

Attach a short summary of key detail fields to each row with aria-describedby, as in announcing detail panel updates.

<li role="option" aria-describedby="sum-17">Lund &amp; Co<span id="sum-17" hidden>Risk high.</span></li>

Step 4 — Choose the detail container (SC 2.1.2)

Permalink to "Step 4 — Choose the detail container (SC 2.1.2)"

Modal dialog, non-modal dialog or region — matched to how the panel looks. See record inspector panels: dialog or region?.

editing ? inspector.showModal() : openPeek(id);

Step 5 — Move between panes (SC 2.4.1)

Permalink to "Step 5 — Move between panes (SC 2.4.1)"

F6 and Shift+F6, skip links and named regions. See keyboard navigation between list and detail panes.

if (e.key === 'F6') { e.preventDefault(); cycle(e.shiftKey ? -1 : 1); }

Step 6 — Resize with a window splitter (SC 2.1.1, 2.5.7)

Permalink to "Step 6 — Resize with a window splitter (SC 2.1.1, 2.5.7)"

See window splitter pattern for resizable panes.

<div role="separator" tabindex="0" aria-label="Invoices list width" aria-valuenow="40" aria-valuemin="20" aria-valuemax="80"></div>
A complete keyboard session in a split view Sequence across list pane, detail pane and splitter: arrow through rows, activate one with Enter, edit in the detail, press F6 back to the list, then Tab to the splitter and collapse the detail with Enter. A complete keyboard session in a split viewList paneDetail paneSplitterDown ×3: detail updates silentlyEnter: focus to headingedit AmountShift+F6: back to row 4TabEnter: detailcollapsed
Every move is deliberate and reversible.

Keyboard interaction contract

Permalink to "Keyboard interaction contract"
Key Context Action Expected AT announcement Failure indicator
Up / Down List Select previous / next Row name (+ summary after pause) Focus jumps to detail
Enter List Activate; focus detail heading Heading naming the record Nothing announced
Escape Detail Return to originating row Row name Focus to body
F6 / Shift+F6 Anywhere Next / previous pane Pane’s last focused element Tab-only movement
Arrows Splitter Resize New value Silent; no change
Enter Splitter Collapse / restore “collapsed” + status No collapse

Screen reader compatibility matrix

Permalink to "Screen reader compatibility matrix"
AT + browser Detail heading on activation Row descriptions Splitter value
NVDA + Firefox Read with level Read after pause Announced
NVDA + Chrome Read Read after pause Announced
JAWS + Chrome Read; may continue reading pane Verbosity dependent Announced
VoiceOver + Safari Read Read after pause Sometimes silent on change
TalkBack + Chrome Read Inconsistent Rarely announced
Narrator + Edge Read Read Announced

Edge cases & failure modes

Permalink to "Edge cases & failure modes"

1. Focus follows selection into the detail

Permalink to "1. Focus follows selection into the detail"

Diagnosis: every arrow press moves focus out of the list. Fix: move focus only on explicit activation.

2. Escape swallowed or overloaded

Permalink to "2. Escape swallowed or overloaded"

Diagnosis: Escape closes a combobox and also returns to the list in one press. Fix: treat Escape as “back to list” only when nothing inside the pane handled it.

3. Drawer that looks open but traps focus

Permalink to "3. Drawer that looks open but traps focus"

Diagnosis: a non-modal panel implemented with a focus trap. Fix: a non-modal dialog without a trap, plus F6.

4. Lost row after list refresh

Permalink to "4. Lost row after list refresh"

Diagnosis: the stored row element was replaced. Fix: store the row ID and look it up on return.

5. Magnifier users cannot widen the table

Permalink to "5. Magnifier users cannot widen the table"

Diagnosis: drag-only divider. Fix: window splitter with Enter to collapse.

Focus handoff in master-detail layouts

Permalink to "Focus handoff in master-detail layouts"

Arrows browse and Enter commits. Focus goes to a heading that names the record, and Escape brings users back to the same row. See focus handoff in master-detail layouts.

<h2 id="detail-heading" tabindex="-1">Lund &amp; Co — INV-1042</h2>

Announcing detail panel updates

Permalink to "Announcing detail panel updates"

Decide whether the detail adds what the row lacks; if so, expose a short summary on demand with a description, not a full-pane announcement. See announcing detail panel updates.

setTimeout(() => { status.textContent = summaryFor(id); }, 700); // fallback only

Record inspector panels — dialog or region?

Permalink to "Record inspector panels — dialog or region?"

Match semantics to appearance: dimmed background means modal; a panel beside a usable table means non-modal; a permanent pane means region. See record inspector panels: dialog or region?.

<aside role="dialog" aria-labelledby="peek-h">…</aside> <!-- non-modal: no aria-modal -->

Keyboard navigation between list and detail panes

Permalink to "Keyboard navigation between list and detail panes"

F6 cycles panes, restoring each pane’s last focus; skip links and named regions serve users who do not know F6. See keyboard navigation between list and detail panes.

<a class="skip-link" href="#detail-h">Skip to record details</a>

Window splitter pattern for resizable panes

Permalink to "Window splitter pattern for resizable panes"

A focusable separator with a value, arrow keys and Enter to collapse, plus a non-drag control. See window splitter pattern for resizable panes.

if (e.key === 'Enter') value > 0 ? collapse() : restore();

Cross-cutting concerns

Permalink to "Cross-cutting concerns"

Responsive collapse. Below a breakpoint, most split views stack or replace the list with the detail. At that point activation becomes navigation: focus the detail heading as if a new page loaded, provide a Back control, and restore the list row on return. The same rules apply at 400% zoom on desktop, where the layout reflows to the narrow form — test it with zoom and reflow testing at 400 percent.

Routing. If selecting a record changes the URL, decide whether selection or only activation pushes history. Pushing on every arrow press floods history; replacing on selection and pushing on activation keeps Back meaningful.

Asynchronous details. Details often load after selection. Show a loading state in the pane, mark it aria-busy, and do not move focus into a pane that has not rendered its heading yet. If activation happens before the load completes, focus the heading once it exists.

Unsaved changes. Selecting another row while the detail has unsaved edits must not silently discard them. Ask in a modal confirmation, and return focus to the list row the user tried to select if they choose to discard.

Behaviour across viewport sizes Matrix of wide desktop, narrow desktop or 400 percent zoom, and mobile against list and detail layout, activation behaviour and back navigation. Behaviour across viewport sizesLayoutActivationBackWide desktopSide by sideFocus to detail headingEscape or F6Narrow or 400% zoomStackedDetail replaces listBack button, row restoredMobileSeparate screensNavigate to detailHistory back, row restored
At narrow widths the split view becomes two pages; focus rules follow.

Worked example: an invoice triage screen

Permalink to "Worked example: an invoice triage screen"

Consider an accounts-payable screen: a list of 300 invoices on the left, the selected invoice on the right with its line items, approval history and an Approve button. Here is how the patterns combine for a screen reader user working through the queue.

They arrive on the page, which announces its heading and the list region. Pressing Down moves through invoices; each row reads supplier, amount and due date, and after a short pause a description adds “Duplicate suspected” for the few rows where that applies. On an interesting row they press Enter: focus moves to the detail heading, “INV-1042, Lund & Co”, and they read the line items with table navigation. They Tab to Approve and activate it; the status region says “Approved. 299 invoices remaining”, and focus returns to the list on the next invoice, because approving removed the current one. When the line-item table is too wide at their zoom level, they Tab to the splitter and press Enter to collapse the list, then press Enter again to restore it.

Nothing in that session required a mouse or a guess about where focus had gone. Every move was the result of a key they chose, and every change that mattered was either where focus landed or in a short status message.

One invoice, start to finish Timeline of a keyboard user's interaction: browse with arrows, pause for the summary, activate with Enter, read line items, approve, focus returns to the next invoice, and optional collapse of the list via the splitter. One invoice, start to finish0 sBrowseDown arrows through invoices; rows announced4 sPause"Duplicate suspected" description read6 sActivateEnter: "INV-1042, Lund & Co, heading level 2"20 sApprove"Approved. 299 invoices remaining"; focus on next invoice25 sMake roomsplitter Enter: list collapsed for the wide table
Each step corresponds to one pattern in this topic.

Who is affected, and how

Permalink to "Who is affected, and how"
User group Typical failure in split views Pattern that fixes it
Screen reader users Detail changes unheard; focus jumps unexpectedly Activation-only focus moves; summaries
Keyboard users Tabbing through the whole list to reach the detail F6 and skip links
Magnifier users Wide tables do not fit beside the other pane Splitter with collapse
Users with cognitive disabilities Focus moves without a clear cause Predictable, user-initiated moves
Switch users Many steps between panes F6 and direct pane shortcuts

Design system integration

Permalink to "Design system integration"

A MasterDetail or SplitView component should encode the decisions in this topic:

  • An API that distinguishes onSelect from onActivate, so focus moves only on activation.
  • A detail slot that requires a heading and receives focus on activation.
  • Named panes, F6 cycling with last-focus memory, and generated skip links.
  • A splitter implementing the window splitter pattern, with persistence and collapse.
  • Variants — region, peek (non-modal dialog), modal — chosen by name, each owning its focus and Escape rules.
  • Responsive behaviour that switches to page-style navigation at a breakpoint, including Back and row restoration.

Tokens are needed for the selected row in the list while focus is in the detail (it must remain visible, at 3:1 against the row background), the splitter grip and its focus ring.

Testing checklist

Permalink to "Testing checklist"

Automated

Permalink to "Automated"

Keyboard

Permalink to "Keyboard"

Screen reader

Permalink to "Screen reader"

FAQ

Permalink to "FAQ"
Should the detail update as the user arrows through the list?

It can — that is what sighted users expect — as long as focus stays in the list. Screen reader users hear the row and, optionally, a short summary.

Is a split view harder to make accessible than separate pages?

Slightly, because two regions share one page. In return it can be faster for keyboard users than page navigation. The patterns here close the gap.

How do I choose between F6 and skip links?

Use both. F6 is fast and expected by experienced users; skip links are discoverable and work for everyone else.

What is the most common master-detail bug?

Moving focus into the detail on every selection change. It makes the list unusable from the keyboard and violates 3.2.1.

Should the list show which record is open in the detail?

Yes. Keep the opened row visibly selected (with a non-colour cue such as a left border or check icon) and expose it with aria-selected or aria-current, so users returning from the detail know which row they left. This matters most for magnifier users, who may see only the list after collapsing the detail.

How do I test a split view quickly?

Use only the keyboard: arrow through ten rows, open one, Escape back, press F6 twice, and collapse a pane with the splitter. Then repeat with a screen reader running. Any moment where you are unsure where focus is, or where a change was silent, is a bug.

Permalink to "Related"

← Back to Accessible Data Tables & Grid Systems