A .panes (or .pane-layout, or
.list-detail) is the container. Two or three
.pane children are the surfaces. The component has no
JavaScript, so the HTML structure sets the layout.
Tokens follow the
M3 canonical layouts.
Coplanar is the default, with full-bleed surfaces and a 1dp
outline-variant divider. Compact widths add 16dp inline
margins. Every wider layout uses 24dp margins and 24dp spacers. Give
the container a height. It is height: 100%.
The split is at 840px, measured against the nearest ancestor with
inline-size or size containment. Without one, viewport media rules
apply. To adapt a nested layout, put
container-type: inline-size on the containing column.
Below the split, one pane shows at a time. Google recommends stacking
supporting content below primary content on compact and medium
layouts. This component's one-active-pane behavior is a framework
adaptation.
Semantic child names are aliases of .pane:
.list-pane, .detail-pane,
.primary-pane, .supporting-pane.
Mark the visible pane on compact windows with
active. For optional content that should overlay the
page, use a
side sheet, not a pane.
List-detail
Use .list-detail or a plain .panes for the
default layout, a 360dp list pane beside a flexible detail pane.
When people pick a row, the detail pane shows that item.
Pair the list pane with a
list.
Inbox
Brunch this weekend?
I will be in your neighborhood doing errands this weekend. Want to grab brunch on Saturday?
<div style="container-type: inline-size">
<div class="list-detail">
<section class="list-pane">
<header>
<h2>Inbox</h2>
</header>
<ul class="list">…</ul>
</section>
<section class="detail-pane">
<header>
<h2>Subject</h2>
</header>
<div>…</div>
</section>
</div>
</div>
Compact
At Compact widths (below 600px), the layout has 16dp inline
margins. Medium, Expanded, Large, and Extra-large widths use
24dp inline margins and 24dp spacers. On Compact and Medium
widths (below 840px), every pane is hidden except the
active one. If none is active, the first pane shows.
AutoInit() does not start panes, so toggle
active yourself when the user picks a row or taps back.
At Expanded widths both panes show regardless of
active, so the same markup works at every size.
The illustration below always shows one pane so you can see the
swap. Change the width of #pane-column in developer
tools to test the list-detail demo above without resizing the viewport.
Its nearest containing column controls the split.
Inbox
Brunch this weekend?
I will be in your neighborhood doing errands this weekend. Want to grab brunch on Saturday?
list.querySelectorAll('a').forEach((row) => {
row.addEventListener('click', (event) => {
event.preventDefault();
detailPane.classList.add('active');
listPane.classList.remove('active');
});
});
back.addEventListener('click', () => {
detailPane.classList.remove('active');
listPane.classList.add('active');
});
Supporting pane
Add supporting, or use
.supporting-pane-layout, to put a flexible primary pane
next to a 360dp supporting pane on the trailing edge. Use it when the
extra content is secondary and should stay in view beside the main
content, such as a related list, an inspector or an outline.
Add start (or left) to dock the supporting
pane on the leading edge instead.
Article
The primary pane holds the focus of the screen. Supporting
content sits beside it on expanded windows and hides on
compact ones until you mark it active.
A supporting pane is in the page flow. A side sheet overlays the page and can be dismissed.
Contents
<div class="panes supporting">
<section class="primary-pane">…</section>
<section class="supporting-pane">…</section>
</div>
<div class="panes supporting start">…</div>
Equal
Add equal to split two panes 1fr / 1fr. Use it when the
two surfaces have the same weight, such as two documents, a diff,
or an editor and a preview.
Draft
Write in this pane. Both columns grow equally once the applicable container or viewport is at least 840px wide.
Preview
The preview stays in lockstep with the draft. Neither pane is the list; neither is supporting.
<div class="panes equal">
<section class="pane">…</section>
<section class="pane">…</section>
</div>
Three-pane
Add three-pane for a list, a main pane and a supporting
pane. It needs 1200px of container width, or of viewport width
without a query container. The columns are a 360dp list, a flexible
middle and a 360dp supporting pane.
Between 840px and 1200px the default two-column list-detail
grid still applies, so the third pane wraps. Below 840px it
uses the same single-pane active pattern as the
others.
Brunch this weekend?
I will be in your neighborhood doing errands this weekend. Want to grab brunch on Saturday?
Details
-
Ali Connors
From
-
Sat, 10:00
When
<div class="panes three-pane">
<section class="list-pane">…</section>
<section class="detail-pane">…</section>
<section class="supporting-pane">…</section>
</div>
Resizing
Write a <button class="drag-handle"> between the
two panes of a list-detail or supporting layout to let people resize
them at 840px and up. The handle replaces the spacer and the divider,
and resizes the list or supporting pane.
People can drag the handle. Left or Right
moves it 16px (Shift for 64px), and Home or
End moves it to the narrowest or widest. Clicking it, or
pressing Enter, steps through narrow, middle and wide.
The click is the single-pointer alternative to dragging. A resized
pane stays at least 240px wide, and the other pane keeps
--md-comp-pane-primary-min-width.
The handle writes the new width to --md-comp-pane-list-width
or --md-comp-pane-supporting-width on the layout. Save
that value to restore it later. The root bundle starts the
PaneResize behavior on load. With the modular entry,
call PaneResize.Init(). Equal and three-pane layouts
do not resize.
From script, Expressive.PaneResize.width(handle) returns
the resizable pane's current width in pixels, or null
when the handle is not in a layout that resizes.
Expressive.PaneResize.setWidth(handle, width) sets it
within the same bounds as dragging. Use it to restore a saved width.
<div class="panes list-detail">
<div class="list-pane">…</div>
<button type="button" class="drag-handle" aria-label="Resize list"></button>
<div class="detail-pane">…</div>
</div>
Appearance
Coplanar is the default. The panes share one surface, with a
1dp divider on expanded windows. Add separated (or
floating) to inset the panes 24dp, round them to
16dp, and fill each with
surface-container-low.
On a separated pane, add elevated for elevation 1.
Add outlined to switch the fill to surface
and draw a 1dp outline.
Outlined
Surface, 1dp outline-variant stroke, no elevation.
Elevated
Surface-container-low at elevation 1.
<div class="panes equal separated">
<section class="pane outlined">…</section>
<section class="pane elevated">…</section>
</div>
A pane
Each pane is a column. A child <header>
(or .pane-header) is the 64dp top bar: icon-only
buttons, then a title-large heading. A child
<main>, .pane-content,
.pane-body, or <div> is the
scrolling body. A last-child <footer>,
.pane-footer, or <nav> is the
56dp action bar.
A direct .list fills the pane with no extra
padding, so list rows can run edge to edge under the header.
<section class="pane">
<header>
<button type="button" aria-label="Back">
<span class="material-symbols" aria-hidden="true">arrow_back</span>
</button>
<h2>Headline</h2>
<button type="button" aria-label="More">
<span class="material-symbols" aria-hidden="true">more_vert</span>
</button>
</header>
<div>…</div>
<footer>
<button type="button" class="text">Action</button>
</footer>
</section>
Tokens
| Token | Default |
|---|---|
--md-comp-pane-margin |
16px Compact; 24px Medium and wider |
--md-comp-pane-gap | 24px |
--md-comp-pane-divider-color | outline-variant |
--md-comp-pane-container-color | surface |
--md-comp-pane-container-shape | 0px (16px separated) |
--md-comp-pane-list-width | 360px |
--md-comp-pane-supporting-width | 360px |