Admin Shell

admin-shell

Behavior-only application shell. Wires sidebar toggles, resize handles, a Cmd+K command palette, and a ResizeObserver that drives responsive sidebar collapse. The shell binds interactions; the consumer supplies the DOM. The docs site you're reading runs inside one.

Basic shape (legacy)

Author supplies the DOM (sidebars + main column + optional command dialog); the shell binds the interactions. This is the original raw-HTML authoring shape — still fully supported.

<admin-shell mode="rounded"> <aside data-sidebar="leading"> <header>…</header> <section> <nav-ui>…</nav-ui> </section> <footer>…</footer> <div data-resize></div> </aside> <main> <header> <button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost" size="sm"></button-ui> <breadcrumb-ui>…</breadcrumb-ui> </header> <section> <router-ui></router-ui> </section> <footer> <span>Footer</span> </footer> </main> <dialog data-command> <command-ui placeholder="Search..."></command-ui> </dialog> </admin-shell>

Basic shape (bespoke — recommended)

The bespoke shape uses module-namespaced custom elements (<admin-sidebar>, <admin-content>, etc.) per ADR-0023. Each child owns its own behavior + state attributes; queryable from outside via :has(admin-sidebar[collapsed]).

<admin-shell mode="rounded"> <admin-sidebar slot="leading" resizable collapsible> <admin-topbar slot="header"> <span slot="heading">Workspace</span> </admin-topbar> <nav-ui>…</nav-ui> <admin-statusbar slot="footer"> <span>User</span> </admin-statusbar> <div data-resize></div> </admin-sidebar> <admin-content> <admin-topbar slot="header"> <button-ui data-sidebar-toggle="leading" icon="sidebar" variant="ghost" size="sm"></button-ui> <breadcrumb-ui slot="heading">…</breadcrumb-ui> </admin-topbar> <admin-scroll> <admin-page> <admin-page-header> <header-ui><span slot="heading">Page Title</span></header-ui> </admin-page-header> <admin-page-body> <section-ui>…content…</section-ui> </admin-page-body> </admin-page> </admin-scroll> <admin-statusbar slot="footer"> <span>Status</span> </admin-statusbar> </admin-content> <admin-command> <command-ui placeholder="Search..."></command-ui> </admin-command> </admin-shell>

State as attribute

Every queryable state is reflected on the relevant child element. Style cross-cuts via :has():

/* Theme tweak when leading sidebar is collapsed */ admin-shell:has(admin-sidebar[slot="leading"][collapsed]) admin-content { /* … */ } /* Show focus indicator while command palette is open */ admin-shell:has(admin-command[open]) { /* … */ }

JS reads the same attribute: shell.querySelector('admin-sidebar[slot="leading"]').hasAttribute('collapsed'). No threshold math, no getBoundingClientRect.

Properties

PropTypeDefaultDescription
modestringLayout variant: rounded (softens edges), borderless (removes outer chrome). Empty is the default bordered surface. Multiple values allowed (e.g. "rounded borderless").

Behavior wiring

AffordanceAuthor markupWhat the shell does
Sidebar toggle[data-sidebar-toggle="leading"] / [data-sidebar-toggle="trailing"]Click flips [collapsed] on the corresponding sidebar. Persists in localStorage.
Sidebar resize<div data-resize></div> inside the sidebarPointer-drag resizes the aside; bounded by --page-sidebar-min-width / --page-sidebar-max-width CSS custom properties. Snaps below threshold to collapsed.
Command palette<dialog data-command> as a child of the shellCmd+K / Ctrl+K calls showModal() on the inner dialog. Forwards command-select events from the inner <command-ui>.
Responsive collapse(automatic)ResizeObserver collapses sidebars when viewport drops below the breakpoint; restores on widen.

Events

EventDetailFires when
sidebar-toggle{ sidebar, expanded }A sidebar is collapsed or expanded (toggle button, snap, or programmatic).
sidebar-resize{ sidebar, width }As a sidebar is dragged. Debounced on the trailing edge.
command-select{ value }Forwarded from the inner command palette when an option is chosen.

JS API

const shell = document.querySelector('admin-shell'); shell.toggleLeading(); // collapse / expand the leading sidebar shell.toggleTrailing(); // collapse / expand the trailing sidebar shell.openCommand(); // open the Cmd+K dialog shell.closeCommand(); // close it

Slot vocabulary inside the shell

Both authoring shapes are valid simultaneously. The legacy raw-HTML form (aside[data-sidebar], main > header, etc.) keeps working unchanged; consumers can also use the aside-ui / header-ui / section-ui / footer-ui slot vocabulary. Both render identically — the shell's CSS lifts each structural selector into :is(legacy, slot-ui) form, and the JS- side wiring (#sidebarName) reads from either data-sidebar or slot. See ADR-0011 for the migration rationale.

<!-- Slot-vocabulary equivalent of the structure shown above --> <admin-shell mode="rounded"> <aside-ui slot="leading"> <header-ui>…</header-ui> <section-ui> <nav-ui>…</nav-ui> </section-ui> <footer-ui>…</footer-ui> <div data-resize></div> </aside-ui> <main> <header-ui>…</header-ui> <section-ui> <router-ui></router-ui> </section-ui> <footer-ui>…</footer-ui> </main> </admin-shell>