Drawer
A panel that slides in from any edge of the screen. Built on Radix Dialog for focus trap, scroll lock and a11y; tuned for mobile with full-bleed side panels, a bottom-sheet grab handle, and swipe-to-dismiss.
Installation#
Usage#
import {
Drawer, DrawerTrigger, DrawerContent, DrawerHeader,
DrawerBody, DrawerFooter, DrawerTitle, DrawerDescription, DrawerClose,
} from "@/components/ui/drawer"
import { Button } from "@/components/ui/button"
export function Example() {
return (
<Drawer>
<DrawerTrigger asChild>
<Button>Open drawer</Button>
</DrawerTrigger>
<DrawerContent side="right">
<DrawerHeader>
<DrawerTitle>Notifications</DrawerTitle>
<DrawerDescription>You're all caught up.</DrawerDescription>
</DrawerHeader>
<DrawerBody>{/* … */}</DrawerBody>
<DrawerFooter>
<DrawerClose asChild>
<Button variant="ghost">Close</Button>
</DrawerClose>
<Button>Mark all read</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
)
}Sides#
side on DrawerContent picks the edge the panel slides from: top, right (default), bottom, or left. Top and bottom render as sheets with a grab handle; left and right as full-height panels. Every variant animates with the dedicated ease-drawer curve.
Mobile: bottom sheet#
On touch devices the bottom sheet is the native pattern. It rounds its top corners, shows a grab handle, and pads past the home-indicator safe area. It is swipe-to-dismiss by default. Drag the handle or header down and release past ~45% (or flick) to close, handing the exit animation back to Radix. The drag only arms after a few pixels, so taps on the rows still register, and it never starts on the scrollable body, so a drag and a scroll never fight. Disable it with swipeToClose={false}.
Navigate inside the sheet#
A bottom sheet often needs to drill into nested views (an action sheet that opens a sub-panel, a settings menu, a multi-step filter) without ever leaving the sheet. Wrap the pages in DrawerNav and give each a DrawerView: only the active view renders, it enters with a native horizontal push (from the right going forward, the left on back), and the sheet's height animates to fit it. A DrawerNavTrigger drills in; a DrawerNavBack (which renders nothing at the root, so you can place it in a shared header) pops. The stack resets when the sheet closes, and swipe-down still dismisses the whole sheet from any view.
Form panel#
A right-side panel is the desktop home for an edit form; it keeps the underlying page visible for context. Compose labelled Input parts in the DrawerBody and land focus on the first field with onOpenAutoFocus.
Navigation menu#
A left drawer makes a compact, off-canvas navigation menu for small screens. The DrawerBody scrolls independently while the header and footer stay pinned; each link is wrapped in DrawerClose so picking one dismisses the panel.
Sizes#
size caps the panel's cross-axis extent: the width of left/right panels and the height of top/bottom sheets. Panels stay full-bleed below the cap, so on a phone they fill the screen and on desktop they settle into a panel. Four steps: sm, md (default), lg, xl.
Density#
density tightens padding, gaps and the title for application UI; it re-provides itself to the header, body and footer so every part stays in sync. Best driven once via DensityProvider. See Density.