Drawer
A drawer is a Headless UI Dialog whose panel slides in from an edge of
the viewport. It shares the Dialog contract (open /
onClose, focus trap, scroll lock, backdrop) and adds side, size and
the layout parts. The panel is portalled to <body> and renders nothing
on the server.
import Drawer from '@zuilib/components/drawer'
Basic
Drawer renders the backdrop; you place Drawer.Panel inside it and the
header, body and footer parts inside that. Drawer.Close with no children
is an icon button pinned to the header's top-right corner.
Sides
side picks the edge; the slide transition and the panel's border follow
it. The parts read side from context, so nothing else changes.
Sizes
size is the panel's width for left / right and its height for
top / bottom, read from --drawer-size-sm/md/lg (20, 28 and 40rem by
default). full covers the viewport.
Non-dismissible
dismissible={false} swallows Escape and backdrop clicks. The drawer then
closes only through Drawer.Close, which always calls onClose, or by
flipping open yourself.
Props
DrawerProps extends Headless UI DialogProps (initialFocus, role,
autoFocus, unmount, static, …) with:
| Prop | Type | Default | Description |
|---|---|---|---|
openrequired | boolean | — | |
onCloserequired | () => void | — | Called from Drawer.Close, and on Escape / backdrop click while dismissible. Flip open to false in response |
side | 'left' | 'right' | 'top' | 'bottom' | 'right' | The edge the panel is pinned to and slides from |
size | 'sm' | 'md' | 'lg' | 'full' | 'md' | Width (left / right) or height (top / bottom) from --drawer-size-*; full covers the viewport |
dismissible | boolean | true | false ignores Escape and backdrop clicks |
className | string | — | Merged last onto the root Dialog element |
backdropClassName | string | — | Merged last onto the backdrop |
children | ReactNode | — | Drawer.Panel and anything else inside the dialog |
Parts
Each part is also a named export (DrawerPanel, DrawerTitle, …) and
takes className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
Drawer.Panel | DialogPanel props | — | The sheet. Fixed to side, sized by size, slides while closed. Headless UI traps focus inside it |
Drawer.Title | DialogTitle props | — | An <h2> the dialog is labelled by |
Drawer.Description | Description props | — | A <p> the dialog is described by |
Drawer.Header | div props | — | Relative box with end padding reserved for the pinned Drawer.Close |
Drawer.Body | div props | — | The scrolling middle |
Drawer.Footer | div props | — | Actions: stacked (primary on top) on narrow screens, end-aligned row from sm up |
Drawer.Close | Button props + label?: string + children? | label: 'Close' | Calls onClose even when not dismissible. No children: a 32px icon button pinned top-right. With children: a wrapper button with only the focus ring, so wrap a Button |
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="drawer"] | div | Root. data-side, data-size, data-state="open" | "closed", data-dismissible (present while dismissible) |
[data-slot="drawer-backdrop"] | div | Fixed scrim, foreground at 50%, fades |
[data-slot="drawer-panel"] | div | The sheet. data-side, data-size, plus Headless UI data-closed during transitions |
[data-slot="drawer-header"] | div | — |
[data-slot="drawer-title"] | h2 | — |
[data-slot="drawer-description"] | p | — |
[data-slot="drawer-body"] | div | — |
[data-slot="drawer-footer"] | div | — |
[data-slot="drawer-close"] | button | — |
[data-slot="drawer-close-icon"] | svg | The default X, only without children |
/* Consumer CSS: wider drawers everywhere, and no border on the sheet */
:root {
--drawer-size-md: 32rem;
}
[data-slot="drawer-panel"] {
border: 0;
}
Tokens
| Token | Used for |
|---|---|
--drawer-size-sm/md/lg | Panel extent per size |
--background, --foreground | Panel surface and text |
--border | Panel edge, header and footer dividers |
--shadow-elevated | Panel shadow |
--foreground at 50% | Backdrop |
--muted-foreground, --accent, --accent-foreground | Icon close button and its hover |
--ring | Keyboard focus ring |
--duration-slow | Slide and fade |
Accessibility
- Renders a Headless UI
Dialog:role="dialog",aria-modal="true", focus trapped inside the panel and returned to the opener on close, page scroll locked, the rest of the pageinert. Drawer.TitleandDrawer.Descriptionwirearia-labelledbyandaria-describedbyon the dialog; include a title.- Escape closes while
dismissible; a backdrop click too. Withdismissible={false}provide a visibleDrawer.Close. - The icon
Drawer.Closeis a 32px target with a visually hidden name (label, default "Close"). - The slide and fade transitions run through Headless UI's
transition; the panel and backdrop are removed from the DOM when closed. - Because the panel is portalled, subtree token overrides around the opener do not reach it; retheme on
:rootor the.darkclass.