Drawer
Swipeable slide-in panel with snap points for mobile-friendly interactions
The drawer is a swipeable panel that slides in from a screen edge and rests at configurable snap points. Use it for mobile-style bottom sheets, multi-height panels like a music player, or any surface a user drags between heights. For a plain slide-in panel without snap points or drag gestures, use Sheet instead.
Preview
Installation
Usage
Composition
DrawerContent composes the portal, backdrop, viewport, and popup. Use DrawerBody for scrollable content that cooperates with swipe gestures.
Examples
Controlled
Use open and onOpenChange to drive the drawer's open state externally.
Drawer is closed
Directions
Use direction to choose which edge the drawer slides in from.
| Value | Description |
|---|---|
"bottom" | Slides up from the bottom (default). |
"top" | Slides down from the top. |
"left" | Slides in from the left. |
"right" | Slides in from the right. |
Snap Points
Use snapPoints to let the drawer rest at multiple heights.
Pixel-Based Snap Points
Pass pixel strings in snapPoints for fixed heights that ignore content size.
Sequential Snap Points
Set sequentialSnap to stop at each snap point instead of skipping them on fast swipes.
Floating Variant
Use variant="floating" for an inset, modal-like panel with rounded corners and shadow.
Music Player
Combine snap points with --drawer-snap-progress for progressive disclosure: mini player, expanded controls, and full queue.
Scrollable Content
Use DrawerBody for scrollable content that cooperates with swipe gestures.
With Form
Place form fields inside DrawerBody, with actions in DrawerFooter.
Inset Footer
Set footerVariant="inset" on DrawerContent for a separated footer with a muted background and top border.
Sticky Footer
Add className="sticky bottom-0" to DrawerFooter to pin it as the drawer is dragged down.
Nested
Open a drawer from inside another; the parent scales down and dims behind the nested drawer.
Non-Modal
Use modal to control focus trapping and page interaction.
| Value | Description |
|---|---|
true | Focus trapped, backdrop shown, body scroll locked (default). |
"trap-focus" | Focus trapped, but the page stays interactive. |
false | No focus trap, no backdrop; the page is fully interactive. |
Accessibility
Include a DrawerTitle (and ideally a DrawerDescription) inside every DrawerContent so the dialog is named for assistive technology. When the modal drawer opens, focus moves into it and is trapped until it closes; modal="trap-focus" keeps the trap while leaving the page interactive, and modal={false} removes the trap entirely. DrawerHandle is a real button labeled "Close drawer" by default; set preventClose to relabel it "Drawer handle" when it should not dismiss.
API Reference
The Drawer component is built on top of Base UI's Dialog. All Base UI props are supported. The documentation below only covers custom props specific to our implementation.
For the complete Base UI API, see the Base UI Dialog documentation.
Props
Drawer
Root component that manages drawer state and provides context to child components. Wraps Base UI's Dialog.Root.
DrawerContent
Main drawer container with swipe gesture handling and positioning. Composes Dialog.Portal, Dialog.Backdrop, Dialog.Viewport, and Dialog.Popup. Props are forwarded to Dialog.Popup.
DrawerBody
Scrollable content area with adaptive padding based on surrounding elements. Wraps ScrollArea.
DrawerHandle
Visual drag indicator that doubles as a close button. Automatically adjusts orientation based on drawer direction.
Components
| Component | Description |
|---|---|
Drawer | Root component that manages state and provides context |
DrawerTrigger | Button that opens the drawer |
DrawerPortal | Renders drawer content into a portal |
DrawerContent | Main content container with swipe gesture handling |
DrawerHandle | Visual drag indicator (automatically adjusts for direction) |
DrawerHeader | Container for title and description with adaptive padding |
DrawerTitle | Accessible title element |
DrawerDescription | Accessible description element |
DrawerBody | Scrollable content area with adaptive padding |
DrawerFooter | Container for action buttons, supports inset variant via content |
DrawerClose | Button that closes the drawer |
useDrawer | Hook to access drawer context (direction, snap state, etc.) |
Nested Scrollable Content
Use DrawerBody for scrollable content inside the drawer. The component handles overflow automatically and adapts its padding based on surrounding elements (header, footer).
Animating Content Based on Snap Position
The drawer exposes a --drawer-snap-progress CSS custom property that animates from 0 (first snap) to 1 (last snap) as the drawer moves between snap points. This works in all browsers:
- Chrome 115+: GPU-accelerated CSS scroll-driven animation (no React re-renders)
- Other browsers: JavaScript fallback sets the variable automatically
Using the CSS Variable
Use --drawer-snap-progress directly in your styles for crossfade effects:
Or with Tailwind's arbitrary value syntax:
Conditional Logic with Render Props
CSS variables work great for visual properties like opacity, but some properties like pointer-events need conditional logic. Use the render prop pattern for these:
Render Prop Values
| Value | Description |
|---|---|
snapProgress | Progress between snap points (0 = first snap, 1 = last snap) |
dragProgress | Progress toward closed (0 = open, 1 = closed) |
isDragging | Whether currently being dragged/scrolled |
activeSnapPoint | Current active snap point value |