Examples
Default
Filter results
End-anchored, large
Quick view
A wider drawer for record detail panels.
Basic usage
<brio-drawer>
<dialog class="drawer" id="demo-drawer-basic">
<div class="drawer-panel">
<header>
<h3>Preferences</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-basic"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section>
<p>
Drawer body content sits here. The body grows to fill remaining
vertical space and scrolls when content overflows, so headers and
footers stay pinned at the edges.
</p>
<p>
Drop form fields, navigation links, or any long-form content inside.
Native focus trapping handles Tab / Shift+Tab within the drawer.
</p>
</section>
<footer>
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-basic"
command="--dismiss"
>
Cancel
</button>
<button
class="button"
type="button"
commandfor="demo-drawer-basic"
command="--dismiss"
>
Save
</button>
</footer>
</div>
</dialog>
</brio-drawer>
<button
class="button"
type="button"
commandfor="demo-drawer-basic"
command="show-modal"
>
Open drawer
</button>
</div>
</div>
Side
<div>
<brio-drawer>
<dialog class="drawer" id="demo-drawer-start">
<div class="drawer-panel">
<header>
<h3>Navigation</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-start"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section>
<p>Slide-from-start drawer (default). Common for mobile nav.</p>
</section>
</div>
</dialog>
</brio-drawer>
<brio-drawer>
<dialog class="drawer is-end" id="demo-drawer-end">
<div class="drawer-panel">
<header>
<h3>Filters</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-end"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section>
<p>
Slide-from-end drawer. Common for filter panels and detail
drawers.
</p>
</section>
</div>
</dialog>
</brio-drawer>
<div class="cluster is-tight">
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-start"
command="show-modal"
>
Open start drawer
</button>
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-end"
command="show-modal"
>
Open end drawer
</button>
</div>
</div>
</div>
</div>
Size
<div>
<brio-drawer>
<dialog class="drawer is-sm" id="demo-drawer-sm">
<div class="drawer-panel">
<header>
<h3>Small</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-sm"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section><p>Compact nav.</p></section>
</div>
</dialog>
</brio-drawer>
<brio-drawer>
<dialog class="drawer is-lg" id="demo-drawer-lg">
<div class="drawer-panel">
<header>
<h3>Large</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-lg"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section><p>Form-shaped drawer.</p></section>
</div>
</dialog>
</brio-drawer>
<brio-drawer>
<dialog class="drawer is-xl" id="demo-drawer-xl">
<div class="drawer-panel">
<header>
<h3>Extra large</h3>
<button
class="dismiss"
type="button"
commandfor="demo-drawer-xl"
command="--dismiss"
aria-label="Close"
>
×
</button>
</header>
<section><p>Content-heavy panel.</p></section>
</div>
</dialog>
</brio-drawer>
<div class="cluster is-tight">
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-sm"
command="show-modal"
>
Open .is-sm
</button>
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-lg"
command="show-modal"
>
Open .is-lg
</button>
<button
class="button is-outlined"
type="button"
commandfor="demo-drawer-xl"
command="show-modal"
>
Open .is-xl
</button>
</div>
</div>
</div>
</div>
Source comments
Drawer -- side panel overlay using native <dialog>
Structure -- semantic HTML carries the inner regions; .drawer-panel
stays as a class because it's a structural archetype, not a
semantic region (no native element means "the visible inner panel
of a modal"):
<brio-drawer>
<dialog class="drawer" id="my-drawer">
<div class="drawer-panel">
<header> <-- title row, bordered separator
<h3>Title</h3>
<button class="dismiss" commandfor="my-drawer"
command="--dismiss" aria-label="Close">×</button>
</header>
<section> <-- scrollable middle; <article> also accepted
…
</section>
<footer> <-- right-aligned actions row
…
</footer>
</div>
</dialog>
</brio-drawer>
Body element: <section> for generic drawer content (canonical), or
<article> for self-contained compositions. Header/body/footer are
direct children of the panel, not the dialog.
Open with commandfor/command="show-modal". Close with command="--dismiss" (animated)
or command="close" (instant). Animated slide + backdrop fade via @starting-style.
Focus trapping, Escape, and backdrop click from native <dialog> (Escape and
backdrop click are intercepted by <brio-drawer> for the animated close path).
Architecture:
The <dialog class="drawer"> element is a full-viewport transparent
container -- it sits under the backdrop and catches the backdrop-
click dismissal at the viewport level. The *visible* drawer is
<div class="drawer-panel">, a child pinned to one edge via
position: fixed and animated via transform. That split lets the
panel carry the slide animation and chrome (radius, shadow,
border) while the dialog carries the modal semantics. The slide
animation targets the panel; brio-drawer.js queries it for
animated close.
Three states drive the panel transform:
[open] panel at transform: 0 (rest)
:not([open]) panel off-screen (native closed)
.is-closing panel off-screen (animated close path;
added by <brio-drawer>, removed after the
transition completes, then .close() runs)
Browsers without @starting-style or discrete transitions skip
the animation and fall back to instant show/hide. Fully usable.
Reference
Source: src/css/drawer.css
Applies to
.drawer
Classes
| Class | Kind | Description |
|---|---|---|
.is-closing | variant | Animated close state applied by brio-drawer before dialog.close() |
.is-end | variant | Slides from the end (right in LTR) instead of the start |
.no-border | opt-out | Strips all borders from the drawer and its slots |
.is-sm | variant | Narrow drawer at 14rem |
.is-lg | variant | Wide drawer at 24rem |
.is-xl | variant | Extra-wide drawer at 32rem |
Slots
| Slot | Description |
|---|---|
.drawer-panel | Visible sliding container pinned to one edge |
.drawer-panel > header | Title row with bordered separator |
.drawer-panel > :is(section, article) | scrollable middle, grows to fill the panel column |
.drawer-panel > footer | right-aligned action row, pinned to the bottom |
Tokens
| Token | Default | Description |
|---|---|---|
--drawer-width | 18rem | Width of the drawer panel |
--drawer-bg | var(--color-surface) | Background color for the panel |
--drawer-border-color | var(--color-border) | Border color for the panel edge facing the page |
--drawer-border-width | var(--border-width-default) | Border width for the panel edge facing the page |
--drawer-header-border-color | var(--color-border) | Separator border color below the header |
--drawer-footer-border-color | var(--color-border) | Separator border color above the footer |
--drawer-color | var(--color-text) | Text color inside the panel |
--drawer-radius | var(--radius-sm) | Border radius on the panel's page-facing corners |
--drawer-title-font-size | var(--ui-font-size-base) | Font size for the drawer title heading |