Drawer
A Drawer is a panel that slides in from an edge of the screen and that users can close with a swipe.
When to Use
- for a short task on a touch screen that users swipe away, such as “Log time”
- for a panel that rests at set heights, which you list in
snapPoints
When Not to Use
- for a side panel that needs no swipe (use a Sheet)
- for a short form in the center of the screen (use a Dialog)
See Dialog for a comparison of the Dialog, the Alert Dialog, the Sheet, and the Drawer.
Anatomy
| Element | Usage |
|---|---|
| Overlay | Shown when modal |
| Popup | Required |
| Handle | Optional |
| Title* | Required |
| Description | Optional |
| Footer | Optional |
* The title is the Drawer’s accessible name. If the design shows no title, visually hide it and keep it in the markup.
DrawerContent renders the portal, the overlay, and the popup, and showSwipeHandle on Drawer adds the handle. A Drawer has no close button, so place a DrawerClose in its footer. The Buttons in the footer are stacked in the order in which they are written, so write the main action first.
A top or bottom Drawer spans the screen. In the preview, a wrapper with max-w-sm limits its content to 384px.
Behavior
By default, a Drawer is modal: focus stays in the popup, and the page’s scroll is locked. If you want the page to stay interactive, set modal={false} on Drawer, which also removes the overlay. If you want the Drawer to stay open when users click the overlay, set disablePointerDismissal on Drawer.
When a Drawer opens, focus is moved to the popup, not to its first control, and users reach that control with Tab. If you want to change the target, use initialFocus on DrawerContent.
Position and Size
To set the edge of the Drawer and the direction of the swipe that closes it, use swipeDirection on Drawer.
| Direction | Position | Size |
|---|---|---|
down, up | The bottom or the top edge, across the screen | As tall as its content, up to the height of the screen minus 96px |
left, right | That edge, from the top to the bottom | 384px wide from 640px, and 75% of the screen below 640px |
If floating is set on Drawer, the Drawer is inset from the edges of the screen. See Layout for the widths of the other overlays.
swipeDirection is physical: left is the left edge in both directions. See Right-to-Left for how to read the direction.
Swipe
On a touch screen, users can start the swipe inside the content. Mouse users drag the handle to move the Drawer because a drag inside the content selects text. If you want to prevent a swipe that starts on an element from moving the Drawer, set data-base-ui-swipe-ignore on that element.
If showSwipeHandle is set, the handle is displayed on the open edge. The handle is hidden from assistive technology and can’t receive focus.
Snap Points
To set the heights at which a top or bottom Drawer rests, pass a list to snapPoints on Drawer. A number from 0 to 1 is a fraction of the height of the screen, a larger number is a height in pixels, and a string can use px or rem. The popup is then as tall as the screen minus 96px, and a point of 1 shows all of it.
The Drawer opens at the first point. A swipe moves it to another point, and a swipe down from the lowest point closes it.
Overflow
If the content is taller than the Drawer, it scrolls under the handle. To keep the header and the footer in view, see Scroll Long Content.
Accessibility
Base UI’s Drawer sets the role, keeps focus in the popup, and closes when users press Escape. You provide the title and a Button that closes the Drawer.
A Drawer needs a DrawerTitle so that screen readers can announce what it is for, as WCAG 2.2 SC 4.1.2 Name, Role, Value requires.
A Drawer with snapPoints needs a Button that sets snapPoint so that users who can’t drag can move between the
points, as WCAG 2.2 SC 2.5.7 Dragging Movements requires. A drag is the only built-in way to move between them.
Keep a DrawerClose in the popup. The overlay and the handle are hidden from assistive technology, so screen reader users on a touch screen have no other way to close the Drawer.
DrawerClose in the footer, such as “Cancel”, as a Drawer has no close button.Installation
npx shadcn@latest add @summit/drawerUsage
import { Button } from '@/components/ui/button';
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer';
<Drawer>
<DrawerTrigger render={<Button variant="outline" />}>Log time</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Log time</DrawerTitle>
<DrawerDescription>Add the hours you worked on this project today.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose render={<Button />}>Save</DrawerClose>
<DrawerClose render={<Button variant="outline" />}>Cancel</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>;Choose the Edge
Set swipeDirection, showSwipeHandle, and floating on Drawer, not on DrawerContent.
<Drawer swipeDirection="right" showSwipeHandle floating>
<DrawerContent>
<DrawerTitle>Filters</DrawerTitle>
</DrawerContent>
</Drawer>Set Snap Points
To move the Drawer from a Button, keep the current point in the parent with snapPoint and onSnapPointChange.
import { useState } from 'react';
const points = ['148px', 1];
const [point, setPoint] = useState<number | string | null>(points[0]);
<Drawer snapPoints={points} snapPoint={point} onSnapPointChange={setPoint}>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Timer</DrawerTitle>
</DrawerHeader>
<Button variant="ghost" onClick={() => setPoint(1)}>
Show all entries
</Button>
</DrawerContent>
</Drawer>;Scroll Long Content
To scroll long content, add flex-1 and overflow-y-auto to the content between the header and the footer. The footer then stays at the bottom of the Drawer.
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Log time</DrawerTitle>
</DrawerHeader>
<div className="flex-1 overflow-y-auto p-4">{entries}</div>
<DrawerFooter>
<DrawerClose render={<Button />}>Save</DrawerClose>
</DrawerFooter>
</DrawerContent>API Reference
DrawerContent passes its props and its className to the popup, and its children to the content inside it. The parts accept the props of their matching parts in Base UI’s Drawer, where DrawerOverlay is Drawer.Backdrop. DrawerHeader, DrawerFooter, and DrawerSwipeHandle accept the props of a div.
Drawer
| Prop | Type | Default | Description |
|---|---|---|---|
swipeDirection | 'down' | 'up' | 'left' | 'right' | 'down' | The edge of the Drawer and the direction of the swipe that closes it |
modal | boolean | 'trap-focus' | true | Locks the page behind the Drawer. The overlay is rendered only when it is true. |
showSwipeHandle | boolean | false | Adds the handle to the open edge |
floating | boolean | false | Insets the Drawer from the edges of the screen and rounds it |
snapPoints | (number | string)[] | None | The heights at which a top or bottom Drawer rests |
Other props are passed to the root of Base UI’s Drawer. See the Base UI Drawer documentation for open, onOpenChange, snapPoint, onSnapPointChange, and disablePointerDismissal.