App Layouts

Layout patterns for authenticated application UIs. Each shell composes .navbar, .sidebar / .sidebar-nav, and content regions with persistent navigation.

Flex vs grid

Flex works in one dimension at a time. A flex column stacks regions vertically; a flex row places them side by side. To combine both -- a sidebar beside a header-plus-content column -- you nest a flex column inside a flex row. Flex is natural for layouts where regions stack in a clear hierarchy (header on top, then a row of sidebar + main below). Sidebar-width transitions are smooth because flex recalculates the remaining space as the sibling's width changes.

Grid works in two dimensions at once. A named grid-template-areas string places each region declaratively. A full-height sidebar is a region that spans multiple rows (grid-row: 1 / -1). Responsive rearrangement is a single grid-template swap at a breakpoint. Column-track width transitions are less smooth than flex-child widths.

In practice: flex is a good default for simple header-above-row shells. Grid shines when a region spans rows or columns (full-height sidebar, three-column, subheader combinations). All the patterns on this page use grid because each has at least one region that crosses the nesting hierarchy.

Responsive queries in these demos

Every demo lives inside <docs-resize>, so media queries (viewport-bound) would report the full viewport width rather than the preview's. The demos use container queries (@container) with .cq-inline on a parent so the queries resolve against the preview's own width. On a real page, substitute @media queries for @container, and the viewport-bound .hide-above-* / .hide-below-* utilities for .cq-hide-*.

The collapsible-sidebar and split demos include a small inline <script> block that toggles a class on click. Real code would wire that state to its app-layer (routing, a custom element, localStorage persistence); the layout CSS is the reusable part.

Dashboard

Header above, sidebar + main below. Two-row, two-column grid; header spans both columns.

Dashboard

Header above; sidebar hides below the sm breakpoint, hamburger appears.

<style>
    .demo-dashboard { container-type: inline-size; }
    .demo-dashboard-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main";
        min-block-size: 22rem;
    }
    .demo-dashboard-grid > .navbar { grid-area: header; }
    .demo-dashboard-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 10rem;
    }
    .demo-dashboard-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }

    @container (max-width: 36rem) {
        .demo-dashboard-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }
</style>
<docs-resize>
    <div class="demo-dashboard">
        <div class="docs-page-preview demo-dashboard-grid">
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>App</strong>
                    </div>
                    <button class="button is-ghost as-icon" type="button" aria-label="Account" disabled="">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
                            <circle cx="12" cy="7" r="4"></circle>
                        </svg>
                    </button>
                </div>
            </header>
            <aside class="sidebar cq-hide-below-sm">
                <nav class="sidebar-nav" aria-label="Main">
                    <a class="sidebar-link" href="#" aria-current="page">Home</a>
                    <a class="sidebar-link" href="#">Users</a>
                    <a class="sidebar-link" href="#">Settings</a>
                </nav>
            </aside>
            <main>
                <h3>Dashboard</h3>
                <p class="text-muted text--1">Header above; sidebar hides below the <code>sm</code> breakpoint, hamburger appears.</p>
            </main>
        </div>
    </div>
</docs-resize>

Full-height sidebar

Sidebar spans both rows on the left; header and main sit in the second column.

Overview

Sidebar spans both rows at wide widths; collapses out of flow below sm.

<style>
    .demo-fullsidebar { container-type: inline-size; }
    .demo-fullsidebar-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "sidebar header"
            "sidebar main";
        min-block-size: 22rem;
    }
    .demo-fullsidebar-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 10rem;
    }
    .demo-fullsidebar-grid > .navbar { grid-area: header; }
    .demo-fullsidebar-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }

    @container (max-width: 36rem) {
        .demo-fullsidebar-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }
</style>
<docs-resize>
    <div class="demo-fullsidebar">
        <div class="docs-page-preview demo-fullsidebar-grid">
            <aside class="sidebar cq-hide-below-sm">
                <nav class="sidebar-nav" aria-label="Main">
                    <a class="sidebar-link" href="#" aria-current="page">Overview</a>
                    <a class="sidebar-link" href="#">Reports</a>
                    <a class="sidebar-link" href="#">Team</a>
                    <a class="sidebar-link" href="#">Billing</a>
                </nav>
            </aside>
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>Workspace</strong>
                    </div>
                    <button class="button is-ghost as-icon" type="button" aria-label="Account" disabled="">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
                            <circle cx="12" cy="7" r="4"></circle>
                        </svg>
                    </button>
                </div>
            </header>
            <main>
                <h3>Overview</h3>
                <p class="text-muted text--1">Sidebar spans both rows at wide widths; collapses out of flow below <code>sm</code>.</p>
            </main>
        </div>
    </div>
</docs-resize>

Docs sidebar

Brio's own docs-shell shape. Full-height sidebar above lg; sidebar hides and a hamburger opens a drawer below that breakpoint.

Intro

Below sm the sidebar hides and a hamburger opens a drawer (inert here; wire to <brio-drawer>).

<style>
    .demo-docsshell { container-type: inline-size; }
    .demo-docsshell-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "sidebar header"
            "sidebar main";
        min-block-size: 22rem;
    }
    .demo-docsshell-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 10rem;
    }
    .demo-docsshell-grid > .navbar { grid-area: header; }
    .demo-docsshell-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }

     
    @container (max-width: 36rem) {
        .demo-docsshell-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
        .demo-docsshell-grid > .navbar,
        .demo-docsshell-grid > main { grid-column: 1; }
    }
</style>
<docs-resize>
    <div class="demo-docsshell">
        <div class="docs-page-preview demo-docsshell-grid">
            <aside class="sidebar cq-hide-below-sm">
                <header>
                    <strong>Docs</strong>
                </header>
                <nav class="sidebar-nav" aria-label="Documentation">
                    <a class="sidebar-link" href="#" aria-current="page">Intro</a>
                    <a class="sidebar-link" href="#">Tokens</a>
                    <a class="sidebar-link" href="#">Components</a>
                    <a class="sidebar-link" href="#">Patterns</a>
                </nav>
            </aside>
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>Brio</strong>
                    </div>
                </div>
            </header>
            <main>
                <h3>Intro</h3>
                <p class="text-muted text--1">Below <code>sm</code> the sidebar hides and a hamburger opens a drawer (inert here; wire to <code>&lt;brio-drawer&gt;</code>).</p>
            </main>
        </div>
    </div>
</docs-resize>

Collapsible sidebar (dashboard)

Dashboard shell with a toggle that collapses the sidebar to an icon-rail. Below the breakpoint, the sidebar hides entirely and the hamburger takes over.

Dashboard

Click the toggle in the sidebar header to switch to icon-only mode. Below sm, sidebar hides entirely.

<style>
    .demo-collapse-dash { container-type: inline-size; }
    .demo-collapse-dash-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main";
        min-block-size: 22rem;
    }
    .demo-collapse-dash-grid > .navbar { grid-area: header; }
    .demo-collapse-dash-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 10rem;
        transition: --sidebar-width var(--transition-normal);
    }
    .demo-collapse-dash-grid.is-collapsed > .sidebar {
        --sidebar-width: 3rem;
    }
    .demo-collapse-dash-grid.is-collapsed .sidebar-label,
    .demo-collapse-dash-grid.is-collapsed .sidebar-link-text { display: none; }
    .demo-collapse-dash-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }

    @container (max-width: 36rem) {
        .demo-collapse-dash-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }
</style>
<docs-resize>
    <div class="demo-collapse-dash">
        <div class="docs-page-preview demo-collapse-dash-grid" id="collapse-dash">
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>App</strong>
                    </div>
                    <button class="button is-ghost as-icon" type="button" aria-label="Account" disabled="">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
                            <circle cx="12" cy="7" r="4"></circle>
                        </svg>
                    </button>
                </div>
            </header>
            <aside class="sidebar cq-hide-below-sm">
                <header>
                    <div class="cluster is-between">
                        <strong class="sidebar-label">Menu</strong>
                        <button class="button is-ghost is-sm as-icon" type="button" data-collapse-toggle="" aria-label="Toggle sidebar">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <polyline points="11 17 6 12 11 7"></polyline>
                                <polyline points="18 17 13 12 18 7"></polyline>
                            </svg>
                        </button>
                    </div>
                </header>
                <nav class="sidebar-nav" aria-label="Main">
                    <a class="sidebar-link" href="#" aria-current="page" aria-label="Home">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg>
                        <span class="sidebar-link-text">Home</span>
                    </a>
                    <a class="sidebar-link" href="#" aria-label="Users">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
                        <span class="sidebar-link-text">Users</span>
                    </a>
                    <a class="sidebar-link" href="#" aria-label="Settings">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
                        <span class="sidebar-link-text">Settings</span>
                    </a>
                </nav>
            </aside>
            <main>
                <h3>Dashboard</h3>
                <p class="text-muted text--1">Click the toggle in the sidebar header to switch to icon-only mode. Below <code>sm</code>, sidebar hides entirely.</p>
            </main>
        </div>
    </div>
</docs-resize>
<script>
    (() => {
        const preview = document.getElementById('collapse-dash');
        if (!preview) return;
        preview.addEventListener('click', (e) => {
            if (e.target.closest('[data-collapse-toggle]')) {
                e.preventDefault();
                preview.classList.toggle('is-collapsed');
            }
        });
    })();
</script>

Collapsible sidebar (full-height)

Same collapse behavior in a full-height-sidebar shell.

Overview

Same toggle behavior; sidebar spans both rows when expanded.

<style>
    .demo-collapse-full { container-type: inline-size; }
    .demo-collapse-full-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "sidebar header"
            "sidebar main";
        min-block-size: 22rem;
    }
    .demo-collapse-full-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 10rem;
        transition: --sidebar-width var(--transition-normal);
    }
    .demo-collapse-full-grid.is-collapsed > .sidebar {
        --sidebar-width: 3rem;
    }
    .demo-collapse-full-grid.is-collapsed .sidebar-label,
    .demo-collapse-full-grid.is-collapsed .sidebar-link-text { display: none; }
    .demo-collapse-full-grid > .navbar { grid-area: header; }
    .demo-collapse-full-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }

    @container (max-width: 36rem) {
        .demo-collapse-full-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }
</style>
<docs-resize>
    <div class="demo-collapse-full">
        <div class="docs-page-preview demo-collapse-full-grid" id="collapse-full">
            <aside class="sidebar cq-hide-below-sm">
                <header>
                    <div class="cluster is-between">
                        <strong class="sidebar-label">Workspace</strong>
                        <button class="button is-ghost is-sm as-icon" type="button" data-collapse-toggle="" aria-label="Toggle sidebar">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <polyline points="11 17 6 12 11 7"></polyline>
                                <polyline points="18 17 13 12 18 7"></polyline>
                            </svg>
                        </button>
                    </div>
                </header>
                <nav class="sidebar-nav" aria-label="Main">
                    <a class="sidebar-link" href="#" aria-current="page" aria-label="Overview">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="7" height="9" x="3" y="3" rx="1"></rect><rect width="7" height="5" x="14" y="3" rx="1"></rect><rect width="7" height="9" x="14" y="12" rx="1"></rect><rect width="7" height="5" x="3" y="16" rx="1"></rect></svg>
                        <span class="sidebar-link-text">Overview</span>
                    </a>
                    <a class="sidebar-link" href="#" aria-label="Reports">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"></path><path d="M7 17l4-8 4 4 6-6"></path></svg>
                        <span class="sidebar-link-text">Reports</span>
                    </a>
                    <a class="sidebar-link" href="#" aria-label="Team">
                        <svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
                        <span class="sidebar-link-text">Team</span>
                    </a>
                </nav>
            </aside>
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>Workspace</strong>
                    </div>
                    <button class="button is-ghost as-icon" type="button" aria-label="Account" disabled="">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
                            <circle cx="12" cy="7" r="4"></circle>
                        </svg>
                    </button>
                </div>
            </header>
            <main>
                <h3>Overview</h3>
                <p class="text-muted text--1">Same toggle behavior; sidebar spans both rows when expanded.</p>
            </main>
        </div>
    </div>
</docs-resize>
<script>
    (() => {
        const preview = document.getElementById('collapse-full');
        if (!preview) return;
        preview.addEventListener('click', (e) => {
            if (e.target.closest('[data-collapse-toggle]')) {
                e.preventDefault();
                preview.classList.toggle('is-collapsed');
            }
        });
    })();
</script>

Three-column

Sidebar + main + aside. Aside collapses first as width narrows; sidebar collapses next.

Main

Aside collapses first (below md); a filter button appears on the right. Sidebar collapses next (below sm); hamburger appears on the left. In real code, wire both to drawers.

<style>
    .demo-threecol { container-type: inline-size; }
    .demo-threecol-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "sidebar header header"
            "sidebar main   aside";
        min-block-size: 22rem;
    }
    .demo-threecol-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 9rem;
    }
    .demo-threecol-grid > .navbar { grid-area: header; }
    .demo-threecol-grid > main {
        grid-area: main;
        padding: var(--space-md);
    }
    .demo-threecol-grid > .demo-aside {
        grid-area: aside;
        inline-size: 9rem;
        background: var(--color-surface-sunken);
        border-inline-start: 1px solid var(--color-border);
        padding: var(--space-md);
    }

    @container (max-width: 42rem) {
        .demo-threecol-grid {
            grid-template-columns: auto minmax(0, 1fr);
            grid-template-areas:
                "sidebar header"
                "sidebar main";
        }
        .demo-threecol-grid > .demo-aside { display: none; }
    }

    @container (max-width: 30rem) {
        .demo-threecol-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }
</style>
<docs-resize>
    <div class="demo-threecol">
        <div class="docs-page-preview demo-threecol-grid">
            <aside class="sidebar cq-hide-below-sm">
                <nav class="sidebar-nav" aria-label="Main">
                    <a class="sidebar-link" href="#" aria-current="page">Inbox</a>
                    <a class="sidebar-link" href="#">Sent</a>
                    <a class="sidebar-link" href="#">Drafts</a>
                </nav>
            </aside>
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-sm" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>Mail</strong>
                    </div>
                    <button class="button is-ghost as-icon cq-hide-above-md" type="button" aria-label="Filters" disabled="">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                            <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
                        </svg>
                    </button>
                </div>
            </header>
            <main>
                <h3>Main</h3>
                <p class="text-muted text--1">Aside collapses first (below <code>md</code>); a filter button appears on the right. Sidebar collapses next (below <code>sm</code>); hamburger appears on the left. In real code, wire both to drawers.</p>
            </main>
            <aside class="demo-aside">
                <strong class="text--1">Filters</strong>
                <nav class="sidebar-nav" aria-label="Filters">
                    <a class="sidebar-link" href="#">Unread</a>
                    <a class="sidebar-link" href="#">Starred</a>
                    <a class="sidebar-link" href="#">Attachments</a>
                </nav>
            </aside>
        </div>
    </div>
</docs-resize>

Split (master-detail)

List column on the left, detail column on the right. Below a threshold, the list fills the width and tapping an item opens the detail as an overlay with a back button.

Re: design review

From: sarah@example.com · 2:14 PM

Thanks for pulling these together. I went through the flows and have a few comments on the onboarding path.

<style>
    .demo-split { container-type: inline-size; }
    .demo-split-grid {
        display: grid;
        grid-template-columns: 14rem minmax(0, 1fr);
        grid-template-areas: "list detail";
        min-block-size: 22rem;
        position: relative;
    }
    .demo-split-list {
        grid-area: list;
        border-inline-end: 1px solid var(--color-border);
        overflow-y: auto;
        padding: var(--space-sm);
    }
    .demo-split-detail {
        grid-area: detail;
        overflow-y: auto;
        background: var(--color-surface);
    }
    .demo-split-detail-header {
        display: flex;
        align-items: center;
        gap: var(--space-xs);
        padding: var(--space-sm) var(--space-md);
        border-block-end: 1px solid var(--color-border);
    }
    .demo-split-detail-body { padding: var(--space-md); }
    .demo-split-back { display: none; }
    .demo-split-items {
        list-style: none;
        margin: 0;
        padding: 0;
        display: flex;
        flex-direction: column;
        gap: var(--space-3xs);
    }
    .demo-split-item {
        display: block;
        padding: var(--space-2xs) var(--space-xs);
        border-radius: var(--radius-sm);
        color: var(--color-text);
        text-decoration: none;
    }
    .demo-split-item:hover { background: var(--color-surface-sunken); }
    .demo-split-item[aria-current="page"] {
        background: var(--color-surface-sunken);
        font-weight: 600;
    }
    .demo-split-item-meta {
        display: block;
        font-size: var(--step--1);
        color: var(--color-text-muted);
    }

    @container (max-width: 32rem) {
        .demo-split-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas: "list";
        }
        .demo-split-list { border-inline-end: none; }
        .demo-split-detail { display: none; }
        .demo-split-grid.is-open .demo-split-detail {
            display: block;
            position: absolute;
            inset: 0;
            z-index: 1;
        }
        .demo-split-back { display: inline-flex; }
    }
</style>
<docs-resize>
    <div class="demo-split">
        <div class="docs-page-preview demo-split-grid is-open" id="split-standalone">
            <aside class="demo-split-list" aria-label="Messages">
                <ul class="demo-split-items">
                    <li><a class="demo-split-item" aria-current="page" data-split-open="" href="#"><strong>Sarah Chen</strong><span class="demo-split-item-meta">Re: design review</span></a></li>
                    <li><a class="demo-split-item" data-split-open="" href="#"><strong>Build system</strong><span class="demo-split-item-meta">Deploy succeeded</span></a></li>
                    <li><a class="demo-split-item" data-split-open="" href="#"><strong>Alex Park</strong><span class="demo-split-item-meta">Quick question</span></a></li>
                    <li><a class="demo-split-item" data-split-open="" href="#"><strong>Team weekly</strong><span class="demo-split-item-meta">Agenda for Thursday</span></a></li>
                </ul>
            </aside>
            <article class="demo-split-detail">
                <header class="demo-split-detail-header">
                    <button class="button is-ghost as-icon demo-split-back" type="button" data-split-close="" aria-label="Back">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
                    </button>
                    <strong>Re: design review</strong>
                </header>
                <div class="demo-split-detail-body stack">
                    <p class="text-muted text--1"><strong>From:</strong> sarah@example.com · 2:14 PM</p>
                    <p>Thanks for pulling these together. I went through the flows and have a few comments on the onboarding path.</p>
                </div>
            </article>
        </div>
    </div>
</docs-resize>
<script>
    (() => {
        document.querySelectorAll('[data-split-open]').forEach(a => {
            a.addEventListener('click', (e) => {
                e.preventDefault();
                a.closest('.docs-page-preview')?.classList.add('is-open');
            });
        });
        document.querySelectorAll('[data-split-close]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                e.preventDefault();
                btn.closest('.docs-page-preview')?.classList.remove('is-open');
            });
        });
    })();
</script>

Split inside a dashboard

The split pattern composes inside a dashboard shell. Two container-query contexts do the work: the preview drives the outer dashboard's responsive; main gets its own container-type so the split's collapse threshold resolves against main's width, not the preview's.

Re: design review

From: sarah@example.com

Resize the preview to see both layers collapse: dashboard sidebar first, then the split to list-only.

<style>
    .demo-dash-split { container-type: inline-size; }
    .demo-dash-split-grid {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main";
        min-block-size: 26rem;
    }
    .demo-dash-split-grid > .navbar { grid-area: header; }
    .demo-dash-split-grid > .sidebar {
        grid-area: sidebar;
        inline-size: auto;
        --sidebar-width: 8rem;
    }
    .demo-dash-split-grid > main {
        grid-area: main;
        overflow: hidden;
        container-type: inline-size;  
        position: relative;
    }

    @container (max-width: 42rem) {
        .demo-dash-split-grid {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas:
                "header"
                "main";
        }
    }

    .demo-dash-split-pane {
        display: grid;
        grid-template-columns: 12rem minmax(0, 1fr);
        grid-template-areas: "list detail";
        block-size: 100%;
        position: relative;
    }
    .demo-dash-split-pane > .list {
        grid-area: list;
        border-inline-end: 1px solid var(--color-border);
        overflow-y: auto;
        padding: var(--space-sm);
    }
    .demo-dash-split-pane > .detail {
        grid-area: detail;
        overflow-y: auto;
        background: var(--color-surface);
    }
    .demo-dash-split-pane .back { display: none; }

    @container (max-width: 28rem) {
        .demo-dash-split-pane {
            grid-template-columns: minmax(0, 1fr);
            grid-template-areas: "list";
        }
        .demo-dash-split-pane > .list { border-inline-end: none; }
        .demo-dash-split-pane > .detail { display: none; }
        .demo-dash-split-pane.is-open > .detail {
            display: block;
            position: absolute;
            inset: 0;
            z-index: 1;
        }
        .demo-dash-split-pane .back { display: inline-flex; }
    }
</style>
<docs-resize>
    <div class="demo-dash-split">
        <div class="docs-page-preview demo-dash-split-grid">
            <header class="navbar">
                <div class="cluster is-between">
                    <div class="cluster is-tight">
                        <button class="button is-ghost as-icon cq-hide-above-md" type="button" aria-label="Menu" disabled="">
                            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                                <line x1="3" y1="6" x2="21" y2="6"></line>
                                <line x1="3" y1="12" x2="21" y2="12"></line>
                                <line x1="3" y1="18" x2="21" y2="18"></line>
                            </svg>
                        </button>
                        <strong>Inbox</strong>
                    </div>
                </div>
            </header>
            <aside class="sidebar cq-hide-below-md">
                <nav class="sidebar-nav" aria-label="Mail">
                    <a class="sidebar-link" href="#" aria-current="page">Inbox</a>
                    <a class="sidebar-link" href="#">Sent</a>
                    <a class="sidebar-link" href="#">Drafts</a>
                    <a class="sidebar-link" href="#">Archive</a>
                </nav>
            </aside>
            <main>
                <div class="demo-dash-split-pane is-open" id="split-in-dash">
                    <aside class="list" aria-label="Messages">
                        <ul class="demo-split-items">
                            <li><a class="demo-split-item" aria-current="page" data-split-open="" href="#"><strong>Sarah Chen</strong><span class="demo-split-item-meta">Re: design review</span></a></li>
                            <li><a class="demo-split-item" data-split-open="" href="#"><strong>Build system</strong><span class="demo-split-item-meta">Deploy succeeded</span></a></li>
                            <li><a class="demo-split-item" data-split-open="" href="#"><strong>Alex Park</strong><span class="demo-split-item-meta">Quick question</span></a></li>
                        </ul>
                    </aside>
                    <article class="detail">
                        <header class="demo-split-detail-header">
                            <button class="button is-ghost as-icon back" type="button" data-split-close="" aria-label="Back">
                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"></polyline></svg>
                            </button>
                            <strong>Re: design review</strong>
                        </header>
                        <div class="demo-split-detail-body stack">
                            <p class="text-muted text--1"><strong>From:</strong> sarah@example.com</p>
                            <p>Resize the preview to see both layers collapse: dashboard sidebar first, then the split to list-only.</p>
                        </div>
                    </article>
                </div>
            </main>
        </div>
    </div>
</docs-resize>
<script>
    (() => {
        const pane = document.getElementById('split-in-dash');
        if (!pane) return;
        pane.addEventListener('click', (e) => {
            if (e.target.closest('[data-split-open]')) {
                e.preventDefault();
                pane.classList.add('is-open');
            } else if (e.target.closest('[data-split-close]')) {
                e.preventDefault();
                pane.classList.remove('is-open');
            }
        });
    })();
</script>