Drawer component
Drawer is a slide-in panel from any edge of the viewport — filters, notifications, a details view opened from a table row. It's built on the same overlay stack, focus-trapping, and backdrop machinery as Modal, so the two behave consistently and stack correctly together; the difference is purely presentational (an edge-anchored panel instead of a centered box).
Playground
Change a prop on the right and the preview updates. Switch to Code to copy what you see.
Recipes
Copy-paste patterns for the ways Drawer actually gets used — each one a real, self-contained example.
Slide-over panel
Same controlled shape as Modal — you own isOpen, and onClose fires from every dismissal path. Slides in from the right by default.
const [isOpen, setIsOpen] = useState(false);
<Button variant="ghost" onPress={() => setIsOpen(true)}>Notifications</Button>
<Drawer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Notifications"
position="right"
>
<NotificationsList />
</Drawer>Any edge
position picks the edge it slides from — left, right, top, or bottom. width sizes left/right drawers; height sizes top/bottom ones.
<Drawer isOpen={isOpen} onClose={onClose} position="left" width="320px" title="Filters">
<FilterForm />
</Drawer>
<Drawer isOpen={isOpen} onClose={onClose} position="bottom" height="60%" title="Details">
<DetailsPanel />
</Drawer>Header, body, footer
The same three slots as Modal — title, children, and an actions footer for the confirm/cancel buttons.
<Drawer
isOpen={isOpen}
onClose={onClose}
title="Edit item"
actions={
<>
<Button variant="soft" onPress={onClose}>Cancel</Button>
<Button color="primary" onPress={handleSave}>Save</Button>
</>
}
>
<EditForm />
</Drawer>Basic controlled drawer
Same controlled shape as Modal: you own isOpen, onClose fires from every dismissal path (backdrop click, Escape, the header close button).
const NotificationsDrawer = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button variant="ghost" onPress={() => setIsOpen(true)}>Notifications</Button>
<Drawer isOpen={isOpen} onClose={() => setIsOpen(false)} title="Notifications" position="right">
<NotificationsList />
</Drawer>
</>
);
};Position and sizing
position picks the edge it slides in from (left, right, top, bottom — right is the default). width applies to left/right drawers, height to top/bottom ones; both accept the usual token scale or a raw string/number for an exact size.
<Drawer isOpen={isOpen} onClose={onClose} position="left" width="320px" title="Filters">
<FilterForm />
</Drawer>
<Drawer isOpen={isOpen} onClose={onClose} position="bottom" height="60%" title="Details">
<DetailsPanel />
</Drawer>Header, body, footer
Same three-slot shape as Modal — title for the header, children for the body, actions for the footer. Any of the three can be omitted independently.
<Drawer
isOpen={isOpen}
onClose={onClose}
title="Edit item"
actions={
<>
<Button variant="soft" onPress={onClose}>Cancel</Button>
<Button color="primary" onPress={handleSave}>Save</Button>
</>
}
>
<ItemForm />
</Drawer>Stacking with Modal
Because Drawer shares Modal's overlay stack, opening a Drawer from inside an already-open Modal (or vice versa) layers correctly without any manual z-index coordination, and Escape only dismisses the topmost overlay.
Props
The full public API for Drawer. Every prop is stable and intentionally typed.
Content
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | Header title. |
| children | ReactNode | — | Body content. |
| actions | ReactNode | — | Footer action buttons. |
title
ReactNode
default: —
Header title.
children
ReactNode
default: —
Body content.
actions
ReactNode
default: —
Footer action buttons.
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| blur | boolean | "sm" | "md" | "lg" | number | — | Backdrop blur amount. |
| backgroundOpacity | number | — | Backdrop opacity (0-100). |
blur
boolean | "sm" | "md" | "lg" | number
default: —
Backdrop blur amount.
backgroundOpacity
number
default: —
Backdrop opacity (0-100).
Layout & positioning
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "left" | "right" | "top" | "bottom" | right | Edge the drawer slides in from. |
| width | "xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number | "420px" | Panel width, for left/right positions. |
| height | "xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number | "360px" | Panel height, for top/bottom positions. |
| portal | boolean | true | Renders through a React portal. |
| container | Element | DocumentFragment | null | — | Custom portal mount target. |
| zIndex | number | — | Explicit stacking z-index; auto-computed when unset. |
position
"left" | "right" | "top" | "bottom"
default: right
Edge the drawer slides in from.
width
"xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number
default: "420px"
Panel width, for left/right positions.
height
"xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number
default: "360px"
Panel height, for top/bottom positions.
portal
boolean
default: true
Renders through a React portal.
container
Element | DocumentFragment | null
default: —
Custom portal mount target.
zIndex
number
default: —
Explicit stacking z-index; auto-computed when unset.
Behavior & states
| Prop | Type | Default | Description |
|---|---|---|---|
| isOpen | boolean | — | Controls whether the drawer is open (required). |
| ariaLabel | string | "Drawer" | Accessible name for the drawer region. |
| dismissible | boolean | true | Shows a close button in the header. |
| closeLabel | string | "Close drawer" | Accessible label for the close button. |
| closeOnOutsideClick | boolean | true | Closes on backdrop click. |
| closeOnEscape | boolean | true | Closes on Escape. |
| initialFocusRef | RefObject<HTMLElement | null> | — | Element to focus when the drawer opens. |
isOpen
boolean
default: —
Controls whether the drawer is open (required).
ariaLabel
string
default: "Drawer"
Accessible name for the drawer region.
dismissible
boolean
default: true
Shows a close button in the header.
closeLabel
string
default: "Close drawer"
Accessible label for the close button.
closeOnOutsideClick
boolean
default: true
Closes on backdrop click.
closeOnEscape
boolean
default: true
Closes on Escape.
initialFocusRef
RefObject<HTMLElement | null>
default: —
Element to focus when the drawer opens.
Motion
| Prop | Type | Default | Description |
|---|---|---|---|
| animationDuration | number | — | Enter/exit animation duration in seconds. |
| easing | "ease" | "easeIn" | "easeOut" | "easeInOut" | — | Animation easing curve. |
animationDuration
number
default: —
Enter/exit animation duration in seconds.
easing
"ease" | "easeIn" | "easeOut" | "easeInOut"
default: —
Animation easing curve.
Events
| Prop | Type | Default | Description |
|---|---|---|---|
| onClose | () => void | — | Called to close the drawer (required). |
onClose
() => void
default: —
Called to close the drawer (required).
Styling
| Prop | Type | Default | Description |
|---|---|---|---|
| backdrop | Partial<BackdropProps> | — | Extra props forwarded to the underlying Backdrop. |
| headers | Partial<PageTitleProps> | — | Extra props forwarded to the header's PageTitle. |
| divider | Partial<DividerProps> | — | Extra props for the header/body divider. |
| classNames | Partial<Record<DrawerSlot, string>> | — | Per-slot class overrides (base/header/headerTitle/body/footer/closeButton). |
| className | string | — | Class applied to the root drawer element. |
| style | CSSProperties | — | Inline styles for the root element. |
backdrop
Partial<BackdropProps>
default: —
Extra props forwarded to the underlying Backdrop.
headers
Partial<PageTitleProps>
default: —
Extra props forwarded to the header's PageTitle.
divider
Partial<DividerProps>
default: —
Extra props for the header/body divider.
classNames
Partial<Record<DrawerSlot, string>>
default: —
Per-slot class overrides (base/header/headerTitle/body/footer/closeButton).
className
string
default: —
Class applied to the root drawer element.
style
CSSProperties
default: —
Inline styles for the root element.
Accessibility
- Traps focus inside the drawer while open and restores it to the triggering element on close, same as Modal.
- ariaLabel (defaults to "Drawer") names the drawer region for assistive technology; set it to something specific when title alone doesn't describe the drawer's purpose clearly enough on its own.
- closeOnEscape (on by default) closes the drawer on Escape, respecting the shared overlay stack when multiple overlays are open at once.
- initialFocusRef sends initial focus somewhere specific on open (a search input at the top of a filters drawer, for instance) instead of the drawer's default first-focusable-element behavior.
Best practices
- Use position="right" (the default) for supplementary content read alongside the page (details, filters) and position="bottom" for content that reads more like an action sheet, especially on narrower viewports.
- Keep a Drawer's width/height proportionate to its content — an oversized drawer for a two-field form just wastes space and pushes the actual content further from the edge it slid in from.
- For content that should block the page entirely and demand a decision, prefer Modal — Drawer's edge-anchored layout reads as supplementary, not as the primary focus of the screen.
- Set closeOnOutsideClick={false} for a drawer holding an in-progress, hard-to-recreate form (a multi-field filter set) so an accidental outside click doesn't discard it.