- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"use client"
import * as React from "react"
Use a drawer for short, focused tasks on touch screens, such as confirming a transfer or picking a payee. It slides in from an edge, supports swipe to dismiss, snap points and nested drawers. Pick a dialog instead when the task is on a large screen and does not need swipe gestures.
Installation#
pnpm dlx shadcn@latest add @dots/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.
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. 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. |
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"
Accessibility#
- The drawer renders as a dialog. Focus moves into it on open, stays inside while it is modal and returns to the trigger on close.
Escapecloses the drawer. Outside press closes it unlessdisablePointerDismissalis set; swipe always can.- Always render a
DrawerTitle. Add aDrawerDescriptionwhen the title alone does not explain the task. DrawerSwipeHandleis hidden from assistive technology. Keep a visible close button for people who cannot swipe.
API Reference#
Drawer#
The root. Holds open state, direction and snap points. Other props pass through to the underlying drawer root.
| Prop | Type | Default |
|---|---|---|
swipeDirection | "up" | "right" | "down" | "left" | "down" |
modal | boolean | "trap-focus" | true |
showSwipeHandle | boolean | false |
snapPoints | (number | string)[] | - |
snapPoint | number | string | null | - |
defaultSnapPoint | number | string | null | - |
onSnapPointChange | (snapPoint: number | string | null, eventDetails) => void | - |
snapToSequentialPoints | boolean | false |
open | boolean | - |
defaultOpen | boolean | false |
onOpenChange | (open: boolean, eventDetails) => void | - |
onOpenChangeComplete | (open: boolean) => void | - |
disablePointerDismissal | boolean | false |
actionsRef | RefObject<{ unmount: () => void; close: () => void } | null> | - |
DrawerTrigger and DrawerClose#
Buttons that open and close the drawer. Use render to swap in your own element, for example render={<Button variant="outline" />}.
DrawerContent#
Renders the portal, overlay (when modal is true), viewport and popup. Accepts className and popup props such as initialFocus and finalFocus.
DrawerHeader, DrawerFooter#
Plain div wrappers for the title block and the actions. Accept all div props.
DrawerTitle, DrawerDescription#
Accessible name and description for the drawer. Accept className and heading or paragraph props.
DrawerPortal, DrawerOverlay, DrawerSwipeHandle#
Lower-level parts for custom layouts. DrawerContent already renders them.