Drawer
A swipe-dismissable panel that slides in from any screen edge, built on Base UI. Supports snap points, a grab handle and nested stacking.
Playground
<Drawer showSwipeHandle>
<DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Drawer title</DrawerTitle>
<DrawerDescription>
A short description of the drawer contents.
</DrawerDescription>
</DrawerHeader>
<DrawerFooter className="pt-4">
<DrawerClose render={<Button variant="outline">Close</Button>} />
</DrawerFooter>
</DrawerContent>
</Drawer>Preview
The default drawer slides up from the bottom (swipeDirection="down" means it dismisses downward). Compose it from DrawerHeader, free-form content and a DrawerFooter pinned to the end.
<Drawer>
<DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Drawer title</DrawerTitle>
<DrawerDescription>
A short description of the drawer contents.
</DrawerDescription>
</DrawerHeader>
<div className="p-4 text-sm text-muted-foreground">
Drag the drawer down or press the close button to dismiss.
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline">Cancel</Button>} />
</DrawerFooter>
</DrawerContent>
</Drawer>Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/drawer.jsonUsage
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerSwipeHandle,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"<Drawer>
<DrawerTrigger render={<Button variant="outline">Open</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Title</DrawerTitle>
<DrawerDescription>Description</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose render={<Button variant="outline">Close</Button>} />
</DrawerFooter>
</DrawerContent>
</Drawer>Directions
swipeDirection sets the edge the drawer rests on and the direction it swipes away: down (default, bottom sheet), up (top sheet), left and right (side panels, 75% wide on mobile and 24rem on desktop).
<Drawer swipeDirection="down" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="up" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="left" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="right" showSwipeHandle>...</Drawer>Swipe handle
showSwipeHandle renders a small grab bar on the swipe edge that hints the drawer is draggable – it orients itself automatically to the active direction.
<Drawer showSwipeHandle>
<DrawerTrigger render={<Button variant="outline">Open with handle</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Grab and drag</DrawerTitle>
<DrawerDescription>
The handle at the top hints that the drawer is draggable.
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose render={<Button variant="outline">Close</Button>} />
</DrawerFooter>
</DrawerContent>
</Drawer>Snap points
snapPoints lets a vertical drawer rest at intermediate heights – pass fractions of the viewport and drag between them; the overlay keeps a minimum opacity while snapped.
<Drawer snapPoints={[0.4, 1]} showSwipeHandle>
<DrawerTrigger render={<Button variant="outline">Open with snap points</Button>} />
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Snap points</DrawerTitle>
<DrawerDescription>
Drag to snap between 40% and full height.
</DrawerDescription>
</DrawerHeader>
<div className="flex flex-col gap-4 overflow-y-auto p-4">...</div>
</DrawerContent>
</Drawer>API reference
Drawer wraps the Base UI Drawer root and shares modal, showSwipeHandle, snapPoints and swipeDirection with DrawerContent via context. It also accepts the Base UI root props – open, defaultOpen, onOpenChange and dismissible. Interactive parts (DrawerTrigger, DrawerClose) support the Base UI render prop to swap in your own element; pass nativeButton={false} if it is not a native button.
Drawer
| Prop | Type | Default | Description |
|---|---|---|---|
| swipeDirection | "down" | "up" | "left" | "right" | "down" | Edge the drawer rests on and the direction it swipes away toward. |
| showSwipeHandle | boolean | false | Render a grab bar (DrawerSwipeHandle) on the swipe edge of the popup. |
| snapPoints | (number | string)[] | – | Intermediate resting heights for vertical drawers, e.g. [0.4, 1]. |
| modal | boolean | true | Render the dimmed overlay and block interaction with the page behind; set false for a non-modal drawer. |
| Component | Description |
|---|---|
| DrawerTrigger | Opens the drawer (data-slot="drawer-trigger"); compose your button via the render prop. |
| DrawerContent | Renders the portal, overlay, viewport and the sliding popup (data-slot="drawer-popup") that wraps your content (data-slot="drawer-content"). |
| DrawerOverlay | Backdrop (data-slot="drawer-overlay") whose opacity tracks the swipe progress; rendered automatically when modal. |
| DrawerSwipeHandle | Decorative grab bar (data-slot="drawer-swipe-handle", aria-hidden) that follows the swipe axis and direction. |
| DrawerHeader | Title/description stack (data-slot="drawer-header"); centered for vertical drawers, left-aligned on desktop. |
| DrawerTitle | Accessible heading (data-slot="drawer-title") announced when the drawer opens. |
| DrawerDescription | Muted supporting copy (data-slot="drawer-description"). |
| DrawerFooter | Action stack pinned to the end (data-slot="drawer-footer"). |
| DrawerClose | Dismisses the drawer (data-slot="drawer-close"); compose any button via the render prop. |
| DrawerPortal | Portals drawer parts to the document body (data-slot="drawer-portal"); used internally by DrawerContent. |
Accessibility & styling hooks
Base UI renders the drawer as a modal dialog – focus is trapped while open, Escape and backdrop click dismiss it, and DrawerTitle / DrawerDescription label it for screen readers. Every part exposes a data-slot attribute, and the popup reflects gesture state as data-swipe-direction, data-swipe-axis, data-snap-points, data-swiping and data-nested-drawer-open. CSS variables such as --drawer-swipe-progress, --drawer-height and --drawer-inset are available for advanced styling – the overlay, bleed and stacking effects are all driven by them.