Drawer
A panel that slides in from the edge of the screen with swipe-to-dismiss gestures.
Anatomy
Import and assemble the component:
1import { Drawer } from "@raystack/apsara";23<Drawer side="right">4 <Drawer.Content>5 <Drawer.Header>6 <Drawer.Title />7 <Drawer.Description />8 </Drawer.Header>9 <Drawer.Body />10 <Drawer.Footer />11 </Drawer.Content>12</Drawer>
Usage
Pick the edge it enters from, then decide who owns its open state.
Basic
A drawer slides in from the edge named by side and traps focus while open.
1<Drawer side="right">2 <Drawer.Trigger render={<Button />}>Open Drawer</Drawer.Trigger>3 <Drawer.Content>4 <Drawer.Header>5 <Drawer.Title>Drawer Title</Drawer.Title>6 <Drawer.Description>Drawer description goes here</Drawer.Description>7 </Drawer.Header>8 <Drawer.Body>9 <span>Main content of the drawer</span>10 </Drawer.Body>11 </Drawer.Content>12</Drawer>
Positioning
The Drawer can slide in from different sides of the screen. Swipe-to-dismiss is automatically configured based on the side prop.
1<Flex gap={5}>2 <Drawer side="top">3 <Drawer.Trigger render={<Button />}>Top Drawer</Drawer.Trigger>4 <Drawer.Content side="top">5 <Drawer.Header>6 <Drawer.Title>Top Drawer</Drawer.Title>7 <Drawer.Description>Slides in from the Top</Drawer.Description>8 </Drawer.Header>9 <Drawer.Body>Content here</Drawer.Body>10 </Drawer.Content>11 </Drawer>12 <Drawer side="right">13 <Drawer.Trigger render={<Button />}>Right Drawer</Drawer.Trigger>14 <Drawer.Content side="right">15 <Drawer.Header>
Controlled
Pass open with onOpenChange to own the state — needed when the drawer has to open from elsewhere on the page, or stay open until a save completes.
1(function ControlledDrawer() {2 const [open, setOpen] = React.useState(false);34 return (5 <Flex align="center" gap={5}>6 <Button variant="outline" onClick={() => setOpen(true)}>7 Open from outside8 </Button>9 <Drawer open={open} onOpenChange={setOpen} side="right">10 <Drawer.Content>11 <Drawer.Header>12 <Drawer.Title>Settings</Drawer.Title>13 </Drawer.Header>14 <Drawer.Body>15 <Text size="small">
API Reference
The same header, body and footer shell as Dialog, anchored to an edge.
Root
Groups all parts of the drawer. The side prop determines both the slide direction and the swipe-to-dismiss direction.
Prop
Type
Content
Renders the drawer panel that slides in from a screen edge.
Prop
Type
Header
children: React.ReactNode - Content to render inside the headerclassName: string - Additional CSS class name
Title
- Inherits all Base UI Drawer.Title props
Description
- Inherits all Base UI Drawer.Description props
Body
- Inherits all HTML div element props
Footer
- Inherits all HTML div element props
Slots
Every rendered part carries a stable data-slot attribute for styling and testing:
| Slot | Element |
|---|---|
drawer-backdrop | The overlay behind the drawer |
drawer-viewport | Viewport wrapper that positions the drawer |
drawer-content | The drawer panel (popup) |
drawer-content-body | Content wrapper inside the panel |
drawer-close | The built-in close button (when showCloseButton) |
drawer-header | The Drawer.Header container |
drawer-title | The Drawer.Title element |
drawer-description | The Drawer.Description element |
drawer-body | The Drawer.Body container |
drawer-footer | The Drawer.Footer container |
Accessibility
- Uses
role="dialog"witharia-modal="true". - Focus is trapped within the drawer and restored on close.
- Supports dismissal with Escape key and swipe gestures.
- Default
aria-labelis"Drawer". Passaria-labeloraria-labelledbyonDrawer.Contentto give the dialog a meaningful name. - Close button label defaults to
"Close". Override with thecloseLabelprop for localisation or context-specific copy (e.g."Close settings"). - Respects motion preferences: drawer slide motion is enabled only when
prefers-reduced-motion: no-preference.