Drawer
A drawer is a full-height panel anchored to one side. Like modal(), it is a popover: a button with a matching popovertarget opens it, and the browser handles the backdrop, the outside click and Escape.
Its most common job on a static site is the navigation menu on a phone.
Basic drawer
Settings
fragment(
button({ popovertarget: 'drawer-basic' }, 'Open drawer'),
drawer({ id: 'drawer-basic', title: 'Settings' },
stack({ gap: 'md' },
toggle({ label: 'Pagefind search', checked: true }),
toggle({ label: 'Image optimization', checked: true }),
toggle({ label: 'Server islands' }),
),
),
)Sides
Start
Anchored to the leading edge — the left in a left-to-right language.
End
The default: anchored to the trailing edge.
fragment(
stack({ direction: 'row', gap: 'sm' },
button({ variant: 'outline', color: 'neutral', popovertarget: 'drawer-start' }, 'From the start'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'drawer-end' }, 'From the end'),
),
drawer({ id: 'drawer-start', side: 'start', title: 'Start' },
text({ variant: 'small', tone: 'muted' }, 'Anchored to the leading edge — the left in a left-to-right language.'),
),
drawer({ id: 'drawer-end', title: 'End' },
text({ variant: 'small', tone: 'muted' }, 'The default: anchored to the trailing edge.'),
),
)Width
Any CSS length. It is capped at 90% of the viewport, so a wide drawer still fits a phone.
Narrow
width: 14rem
Wide
width: 34rem
fragment(
stack({ direction: 'row', gap: 'sm' },
button({ variant: 'outline', color: 'neutral', popovertarget: 'drawer-narrow' }, 'Narrow'),
button({ variant: 'outline', color: 'neutral', popovertarget: 'drawer-wide' }, 'Wide'),
),
drawer({ id: 'drawer-narrow', width: '14rem', title: 'Narrow' },
text({ variant: 'small', tone: 'muted' }, 'width: 14rem'),
),
drawer({ id: 'drawer-wide', width: '34rem', title: 'Wide' },
text({ variant: 'small', tone: 'muted' }, 'width: 34rem'),
),
)As a navigation menu
The pattern most sites want: a menu button in the bar, the links in a drawer.
fragment(
appBar({ brand: 'sitelo' },
appBarSpacer(),
appBarActions(
iconButton({
label: 'Open navigation',
variant: 'ghost',
color: 'neutral',
popovertarget: 'drawer-nav',
icon: icon('menu'),
}),
),
),
drawer({ id: 'drawer-nav', title: 'Navigation' },
navLink({ href: '#docs', current: true }, 'Docs'),
navLink({ href: '#ui' }, 'UI'),
navLink({ href: '#examples' }, 'Examples'),
navLink({ href: '#about' }, 'About'),
divider({ spacing: 'sm' }),
button({ block: true }, 'Get started'),
),
)A filter panel
Filters
Type
Tags
fragment(
button({ variant: 'soft', color: 'neutral', popovertarget: 'drawer-filters' }, 'Filters'),
drawer({ id: 'drawer-filters', title: 'Filters', width: '22rem' },
stack({ gap: 'lg' },
choiceGroup({
legend: 'Type',
name: 'drawer-type',
value: 'guide',
options: [
{ value: 'guide', label: 'Guides' },
{ value: 'example', label: 'Examples' },
{ value: 'all', label: 'Everything' },
],
}),
choiceGroup({
legend: 'Tags',
name: 'drawer-tags',
type: 'checkbox',
value: ['routing'],
options: ['routing', 'data', 'islands'],
}),
stack({ direction: 'row', gap: 'sm' },
button({ variant: 'ghost', color: 'neutral', popovertarget: 'drawer-filters', popovertargetaction: 'hide' }, 'Cancel'),
button('Apply'),
),
),
),
)Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | — | Required. What a trigger’s popovertarget points at. |
title | Child | — | Heading, and the dialog’s accessible name. |
side | 'start' | 'end' | 'end' | Which edge it is anchored to. |
width | string | '20rem' | Panel width, capped at 90vw. |
closable | boolean | true | Show the × in the header. |
closeLabel | string | 'Close' | Accessible name for that button. |