Skip to content

Master–detail layout

3 min read · Updated 8 Oct 2026

A master–detail page lists items on one side; picking one fills the rest of the page with that item’s detail. Reflow rules, grid traps and the shared tokens are in Layout and responsiveness.

Master–detail layout: Pick from the list; inspect one region in detail.

Illustrative example with synthetic data. Pick from the list; inspect one region in detail.

Use it when readers compare items by opening them one at a time: an agent, a turbine, a product or a student. The same item opened from another page, without the list, is a detail page.

The page is mainly Use instead
Headline numbers, then the charts that explain them KPI-led
Many items side by side, each as a card Card grid
Rows of records to read, sort or export Table-first
One item with its artwork, picked from picture tiles Showcase
  1. Do readers need figures for all items while one item is open? Use summary band.
  2. Otherwise, use list and detail.
Variant Layout Use cases
List and detail List on the left, the selected item’s KPIs and charts on the right Sales agents: revenue, deals won and win rate, with revenue by month
Summary band KPIs for all items on top, then the list and the selected item’s charts Wind-turbine fleet: fleet output on top, then one turbine’s output by day
  • Write the pick as a selection on the item’s field in the page’s selection group with selectValues; a new pick replaces the previous one. In manual mode it waits for Apply like any other selection. See Driving selections from your own UI.
  • Build the list from your own query, and leave the list’s own field out of its filters, or a pick filters the list down to one item; see Re-query your own objects only on applied changes.
  • Keep the detail objects mounted; they re-query when the applied selections change.
  • With nothing selected, the detail shows every item combined, and a reload loses the selection. Select a default item on load.
  • Name the selected item in the detail heading, at a fixed height with truncation.
  • Render each item as a button and mark the selected one with aria-current="true".
<section class="md-list" aria-label="Agents">
<ul>
<li><button type="button" aria-current="true">Agent A</button></li>
<li><button type="button">Agent B</button></li>
</ul>
</section>
:root {
/* Between 240px and 320px, for your item names. */
--list-w: 280px;
--detail-heading-h: 32px;
}
.board {
display: grid;
gap: var(--gap);
}
.band {
display: grid;
gap: var(--gap);
min-height: 0;
}
.band--kpis {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.band--two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.md {
display: grid;
grid-template-columns: var(--list-w) minmax(0, 1fr);
gap: var(--gap);
min-height: 0;
}
.md-list {
/* The detail sizes the row; the list scrolls inside it. */
contain: size;
min-height: 0;
overflow-y: auto;
}
.md-list ul {
display: grid;
gap: 4px;
margin: 0;
padding: 0;
list-style: none;
}
.md-detail {
display: grid;
gap: var(--gap);
min-height: 0;
}
.md-detail > h2 {
height: var(--detail-heading-h);
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* Two 320px detail charts, 15 for a classic scrollbar:
2 × 16 + 280 + 16 + (2 × 320 + 16) + 15 = 999 */
@media (width < 999px) {
.md {
grid-template-columns: minmax(0, 1fr);
}
/* The list becomes a strip above the detail and scrolls sideways. */
.md-list {
contain: none;
overflow-x: auto;
overflow-y: hidden;
}
.md-list ul {
grid-auto-flow: column;
grid-auto-columns: max-content;
}
}
@media (width < 700px) {
.band--kpis {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.band--two {
grid-template-columns: minmax(0, 1fr);
}
}

.board takes its fixed height from Bands above the board. Past a few dozen items a strip is slow to scan; use a <select> above the detail instead.

┌────────┬──────────────────────────┐
│ Item 1 │ Item 1 │
│ Item 2 ├──────┬──────┬──────┬─────┤
│ Item 3 │ KPI │ KPI │ KPI │ KPI │
│ Item 4 ├──────┴──────┼──────┴─────┤
│ Item 5 │ Chart A │ Chart B │
│ … ├─────────────┼────────────┤
│ │ Chart C │ Chart D │
└────────┴─────────────┴────────────┘
<div class="board md md--list">
<section class="md-list" aria-label="Agents"><!-- buttons --></section>
<section class="md-detail" aria-labelledby="detail-title">
<h2 id="detail-title">Agent A</h2>
<div class="band band--kpis"><!-- 4 KPIs --></div>
<div class="band band--two"><!-- 2 charts --></div>
<div class="band band--two"><!-- 2 charts --></div>
</section>
</div>
.md--list .md-detail {
grid-template-rows: auto auto minmax(0, 1fr) minmax(0, 1fr);
}
/* 64 + 2 × 16 + 32 + 16 + 96 + 16 + 252 + 16 + 252 = 776 */
@media (width < 999px), (height < 776px) {
.md--list {
height: auto;
}
.md--list .md-detail {
grid-template-rows: none;
}
.md--list .band--two {
grid-auto-rows: minmax(var(--chart-min), auto);
}
}
Width List Detail Height
999px and wider Left column KPIs 4 across, charts 2 across Fits the window from 776px tall
700–998px Strip above the detail KPIs 4 across, charts 2 across Scrolls
Under 700px Strip above the detail KPIs 2 across, charts 1 across Scrolls
┌───────┬───────┬───────┬───────────┐
│ KPI │ KPI │ KPI │ KPI │
├───────┴┬──────┴───────┴───────────┤
│ Item 1 │ Item 1 │
│ Item 2 ├─────────────┬────────────┤
│ Item 3 │ Chart A │ Chart B │
│ … ├─────────────┼────────────┤
│ │ Chart C │ Chart D │
└────────┴─────────────┴────────────┘

The summary KPIs show all items, so build them from your own queries and leave the list’s field out of their filters, as for the list. A chart object in the group would follow the pick.

<div class="board board--summary">
<div class="band band--kpis"><!-- 4 KPIs for all items --></div>
<div class="md">
<section class="md-list" aria-label="Turbines"><!-- buttons --></section>
<section class="md-detail" aria-labelledby="detail-title">
<h2 id="detail-title">Turbine 07</h2>
<div class="band band--two"><!-- 2 charts --></div>
<div class="band band--two"><!-- 2 charts --></div>
</section>
</div>
</div>
.board--summary {
grid-template-rows: auto minmax(0, 1fr);
}
.board--summary .md-detail {
grid-template-rows: auto minmax(0, 1fr) minmax(0, 1fr);
}
/* 64 + 2 × 16 + 96 + 16 + 32 + 16 + 252 + 16 + 252 = 776 */
@media (width < 999px), (height < 776px) {
.board--summary {
height: auto;
grid-template-rows: none;
}
.board--summary .md-detail {
grid-template-rows: none;
}
.board--summary .band--two {
grid-auto-rows: minmax(var(--chart-min), auto);
}
}
Width Summary List Detail Height
999px and wider 4 across Left column Charts 2 across Fits the window from 776px tall
700–998px 4 across Strip below the summary Charts 2 across Scrolls
Under 700px 2 across Strip below the summary Charts 1 across Scrolls