- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Direction
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
"use client"
import * as React from "react"The drawer component now uses Base UI instead of Vaul. If you installed the previous version, see the migration guide.
Installation#
pnpm dlx shadcn@latest add drawer
Add the following to your global styles. On iOS Safari, the drawer overlay is absolutely positioned and requires a positioned body to cover the viewport after the page is scrolled. See the Base UI docs for details.
body {
position: relative;
}Usage#
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>Open</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
<DrawerDescription>This action cannot be undone.</DrawerDescription>
</DrawerHeader>
<div className="p-4">{/* Content here */}</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose render={<Button variant="outline" />}>Cancel</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>Composition#
Use the following composition to build a Drawer:
Drawer
├── DrawerTrigger
└── DrawerContent
├── DrawerHeader
│ ├── DrawerTitle
│ └── DrawerDescription
└── DrawerFooterDrawerContent composes the portal, overlay, viewport, and popup from Base UI. For lower-level control, DrawerPortal, DrawerOverlay, and DrawerSwipeHandle are also exported.
Custom Sizes#
A vertical drawer sizes itself to its content and is capped at calc(100dvh - 6rem) by default. A side drawer spans 75% of the viewport width, or 24rem on larger screens.
To customize the height of a vertical drawer, use the h-* and max-h-* utilities on DrawerContent.
<DrawerContent className="h-[50vh]">To customize the width of a side drawer, use the w-* and max-w-* utilities on DrawerContent.
<DrawerContent className="w-96">When the same component renders in multiple directions, scope an override to one axis using the data-[swipe-axis=*] variants.
<DrawerContent className="data-[swipe-axis=y]:max-h-[50vh] data-[swipe-axis=x]:w-96">To make a region of the drawer scrollable, make the scroll container a flex item. Avoid h-full, which does not resolve inside a content-sized drawer.
<DrawerContent>
<DrawerHeader>...</DrawerHeader>
<div className="flex-1 overflow-y-auto p-4">{/* Scrollable content */}</div>
<DrawerFooter>...</DrawerFooter>
</DrawerContent>Styling#
The drawer exposes CSS variables for style-level customization. Set the sizing variables on DrawerContent. Set the overlay variable on [data-slot=drawer-overlay] in your CSS.
| Variable | Default | Description |
|---|---|---|
--drawer-inset | 0px | Floats the drawer from the viewport edges. |
--drawer-bleed-background | var(--color-popover) | Fills the gap behind the drawer on swipe overshoot. |
--drawer-overlay-min-opacity | 0 | Minimum overlay opacity. Defaults to 0.5 when snap points are active. |
The drawer also sets data attributes you can target with variants such as data-[swipe-direction=down]: on DrawerContent, or group-data-[swipe-axis=y]/drawer-popup: on its descendants.
| Attribute | Values | Set when |
|---|---|---|
data-swipe-direction | up, right, down, left | Always. |
data-swipe-axis | x, y | Always. |
data-snap-points | Present | The drawer has snap points. |
data-expanded | Present | The drawer is at the full snap point. |
data-swiping | Present | A swipe is in progress. |
data-nested-drawer-open | Present | A nested drawer is open on top. |
Examples#
Position#
Use the swipeDirection prop to set the side of the drawer.
Available options are up, right, down, and left.
import { Button } from "@/components/ui/button"
import {
Drawer,Swipe Handle#
Use showSwipeHandle on Drawer to render a swipe handle.
"use client"
import { Button } from "@/components/ui/button"Nested#
Open drawers from inside another drawer. Parent drawers stay mounted and stack behind the frontmost drawer.
"use client"
import { useIsMobile } from "@/hooks/use-mobile"Non Modal#
Set modal={false} to allow interaction with the rest of the page while the drawer is open. Combine with disablePointerDismissal to prevent the drawer from closing on outside presses. Use modal="trap-focus" to keep focus inside the drawer while leaving scroll and pointer interaction unrestricted.
import { Button } from "@/components/ui/button"
import {
Drawer,Snap Points#
Use snapPoints to snap a drawer to preset heights. Numbers between 0 and 1 represent fractions of the viewport. Numbers greater than 1 are treated as pixel values. String values support px and rem units. Snap points apply to vertical drawers.
Track the active snap point with the controlled snapPoint and onSnapPointChange props. At the full snap point, the drawer gets a data-expanded attribute you can style with the data-expanded: variant.
"use client"
import { Button } from "@/components/ui/button"Responsive#
You can combine the Dialog and Drawer components to create a responsive dialog. This renders a Dialog component on desktop and a Drawer on mobile.
"use client"
import * as React from "react"Migrating from Vaul#
The base drawer now uses Base UI instead of Vaul. If you installed the previous base drawer, update your usage to the Base UI API.
Update the dependency.
- npm install vaul
+ npm install @base-ui/reactReplace direction with swipeDirection.
Use down instead of bottom, and up instead of top. left and right
stay the same.
- <Drawer direction="bottom">
+ <Drawer swipeDirection="down">Replace asChild with render.
For DrawerTrigger, pass the trigger element to the render prop.
- <DrawerTrigger asChild>
- <Button variant="outline">Open</Button>
- </DrawerTrigger>
+ <DrawerTrigger render={<Button variant="outline" />}>
+ Open
+ </DrawerTrigger>For DrawerClose, pass the close element to the render prop.
- <DrawerClose asChild>
- <Button variant="outline">Cancel</Button>
- </DrawerClose>
+ <DrawerClose render={<Button variant="outline" />}>
+ Cancel
+ </DrawerClose>Update snap point props.
If you use snap points, rename the controlled snap point props and the sequential snap point prop.
<Drawer
snapPoints={[0.25, 0.5, 1]}
- activeSnapPoint={snapPoint}
- setActiveSnapPoint={setSnapPoint}
- snapToSequentialPoint
+ snapPoint={snapPoint}
+ onSnapPointChange={setSnapPoint}
+ snapToSequentialPoints
>Update animation and focus props.
- <Drawer onAnimationEnd={(open) => setDone(open)}>
+ <Drawer onOpenChangeComplete={(open) => setDone(open)}>- <DrawerContent onOpenAutoFocus={(event) => event.preventDefault()}>
+ <DrawerContent initialFocus={false}>Review Vaul-only props.
Vaul props like handleOnly, repositionInputs, and
shouldScaleBackground do not have one-to-one replacements in the base drawer
API. Use Base UI props such as disablePointerDismissal, modal, snapPoints,
or controlled open state for the behavior you need.
- <Drawer handleOnly repositionInputs={false} shouldScaleBackground>
+ <Drawer>- <Drawer dismissible={false}>
+ <Drawer disablePointerDismissal>Update custom data attribute selectors.
Replace Vaul's data-vaul-drawer-direction selectors with Base UI's
data-swipe-direction selectors.
- <DrawerContent className="data-[vaul-drawer-direction=bottom]:max-h-[50vh]">
+ <DrawerContent className="data-[swipe-direction=down]:max-h-[50vh]">Base UI also exposes attributes like data-swiping, data-starting-style, and
data-ending-style for swipe and transition states. Descendants inside
DrawerContent can use group-data-[swipe-axis=x]/drawer-popup and
group-data-[swipe-axis=y]/drawer-popup for axis-specific styling.
API Reference#
See the Base UI documentation for the full API reference.