Sidebar (responsive layout pattern)

A sidebar is a CSS layout pattern, not a custom element. At ≥ lg (48em / 768px) the sidebar sits next to the main content as a persistent <aside>. Below that the aside is hidden and a hamburger button opens the same content inside a <ui-dialog>. CSS handles the swap; JS just opens the dialog when the button is clicked — no viewport detection in script.

Live demo

Resize the window across 48em (768px). At ≥ 48em the shell shows a persistent <aside>; below that, the aside disappears and the hamburger button in the top bar becomes visible. Click it to open the nav inside a dialog. (The dialog also auto-converts to a bottom-sheet presentation at ≤ 640px — that's a built-in <ui-dialog> behavior, see Dialog §6 Sizing.)

Page title

Main content

Resize the viewport across 48em (768px) — the aside collapses below 48em and the hamburger button appears in the top bar.

Sections

HTML

The persistent <aside> and the dialog hold the same content. For static nav items duplicating the markup is fine. If the list is dynamic, render once into a <template> and clone into both, or move the same DOM between them on toggle.

Show code <div class="shell"> <div class="topbar"> <button class="btn outline btn-icon toggle" aria-label="Open navigation"> <ui-icon name="list"></ui-icon> </button> <h2>Page title</h2> </div> <aside aria-label="Sections"> <ul class="nav">…</ul> </aside> <main>…</main> <ui-dialog> <dialog> <header> <h3>Sections</h3> <div class="dialog-header-decoration"></div> <button dialog-dismiss aria-label="Close"><ui-icon name="x"></ui-icon></button> </header> <main> <ul class="nav">…</ul> </main> </dialog> </ui-dialog> </div>

CSS

Two-column grid at lg and above; single-column with the aside hidden below. The hamburger has the inverse visibility — visible only when the aside is hidden.

Show code .shell { display: grid; grid-template-areas: 'topbar topbar' 'aside main'; grid-template-columns: auto 1fr; grid-template-rows: auto 1fr; } .shell > .topbar { grid-area: topbar; } .shell > aside { grid-area: aside; inline-size: 14rem; } .shell > main { grid-area: main; } .shell .toggle { display: none; } /* TODO(future): switch to @media (--lg-n-below) once PostCSS Custom Media lands. https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media */ @media (width < 48em) { /* lg-n-below */ .shell { grid-template-areas: 'topbar' 'main'; grid-template-columns: 1fr; } .shell > aside { display: none; } .shell .toggle { display: inline-flex; } }

JS

Just open the dialog when the button is clicked. No viewport detection, no matchMedia. The hamburger button is only visible when the aside is hidden, so a click always means "open the drawer."

Show code const dialog = root.querySelector('ui-dialog') root.querySelector('.toggle').addEventListener('click', () => dialog.open())

Why this layout uses a documented pattern

A sidebar has no element of its own in the systems this library maps to: the cross-system reference lists it under Layout as plain CSS layout, with Bootstrap's Offcanvas as the nearest packaged equivalent. A <ui-sidebar> here would mostly forward to <aside> and a media query, and this library uses plain CSS when a wrapper component would add no behavior. This guide documents the pattern so each application can choose its own structure.

Accessibility

See also