Search for a command to run...
A draggable bottom sheet (or side panel) built on `vaul`. Slides in from any edge of the viewport, dismisses on swipe or outside-click. Use on mobile for action sheets and quick forms; on desktop, reach for `Dialog` unless the content benefits from being side-anchored.
Pass direction="bottom" | "top" | "left" | "right" on Drawer to anchor the sheet. The visible floating card pulls in inset-2 from the screen edge so the outer touch surface stays full-bleed — that's intentional, the gap is part of vaul's drag-to-dismiss surface.
DrawerWraps every drawer instance. Forwards all vaul Drawer.Root props — most importantly direction, open / onOpenChange (controlled), and shouldScaleBackground.
| Prop | Type | Default | Description |
|---|---|---|---|
direction | "bottom" | "top" | "left" | "right" | "bottom" | Which edge the sheet slides in from. |
open | boolean | — | Controlled open state. |
onOpenChange | (open: boolean) => void | — | Fires when the drawer wants to open / close. |
shouldScaleBackground | boolean | false | Apple-style scale of the page behind the drawer. Bottom direction only. |
dismissible | boolean | true | When false, outside-click and swipe-dismiss are disabled. |
DrawerTrigger · DrawerClosePass-through wrappers around vaul's Trigger / Close. Use asChild to render a Button (or any element) as the open / close affordance.
DrawerContentThe floating card. Direction-aware: bottom and top sheets fill width and cap at 80vh; left and right sheets fill height and cap at max-w-sm on sm breakpoints (override with className). On bottom direction, renders a centered drag handle inside the card.
DrawerHeader · DrawerFooterLayout slots for short, non-scrolling drawers. Header centers its title on bottom and top drawers (sheet feel) and left-aligns on side drawers (panel feel). Footer pins to the bottom of the card with column-stacked actions.
For scrollable forms, prefer the floating-chrome pattern instead (see the "Scrollable form" variant): make DrawerContent the surface itself (p-0 + a definite height via inset-y on side drawers / h-[90dvh] on bottom), fill it with one overflow-y-auto scroll body padded to clear the chrome, then float a header and footer over the top/bottom edges — each a gradient scrim (gradient.primary) layered with a progressiveBlur.top / progressiveBlur.bottom backdrop-blur-md. Render the chrome after the body in the DOM so it paints above without z-index, mark the chrome containers pointer-events-none, and re-enable pointer-events-auto on the buttons. This is the same pattern documented on Dialog's scrollable example.
Two drawer-specific gotchas:
progressiveBlur only fades cleanly on side panels. Inside a direction="bottom" sheet (a transformed, clipped layer) the engine drops the mask over a backdrop-filter, so the blur renders flat. If you need the fading blur on a bottom sheet, swap in a mask-free fallback there (stack nested backdrop-filter layers anchored to the edge so the ramp comes from box geometry).h-16 sm:h-24 / h-24 sm:h-32). The drawer fills the viewport, so a fixed desktop-sized scrim eats a huge fraction of a small sheet. Match the scroll body's pt/pb to the scrim height per breakpoint.DrawerTitle · DrawerDescriptionRequired-by-accessibility text labels. Wrap vaul's Title and Description and apply canonical typography (text-base font-medium text-fg-high / text-sm text-fg-mid).
Use direction="bottom" for mobile-first action sheets — destructive confirmations, quick
pickers, anything that needs the user to commit before continuing.
Use direction="right" (or "left") for inspector panels alongside a list — citations, row
details, side-by-side editing. Override the default max-w-sm with className when you need a
wider panel.
For scrollable forms, use the floating-chrome pattern: one overflow-y-auto body padded to
clear a fixed header and footer that float over the edges with a gradient scrim + progressive
blur (the "Scrollable form" variant). Content fades under the chrome instead of cutting off at a
hard line — matching Dialog's scrollable example. Give the panel a definite height so the body
can scroll.
Show the header close (✕) on desktop only (hidden sm:inline-flex). On mobile the drag handle
and swipe-to-dismiss already cover it, so a close button is redundant chrome.
Prefer size="xl" (or lg) on footer buttons. The footer is the commit surface — tall buttons
read as the primary action against the body, and on mobile the bigger hit target matches the
drawer's thumb-zone affordance.
Reach for Drawer on desktop when a Dialog would do. Dialog is centered, modal, and avoids
the slide-in animation cost when there's no side-anchored content.
Stack a Drawer inside another Drawer. If you need a second layer, route to a new screen or use a
nested Dialog — overlapping drawers fight each other's dismiss gestures.
Skip DrawerTitle because the visible header has a different heading. Accessibility relies on
the title being present; hide it with sr-only if the visual design omits it.