Drawer
StableA touch-first panel that slides from an edge, follows the finger, rests on snap points and dismisses with a swipe.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/drawer.jsonUsage
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"<Drawer showSwipeHandle>
<DrawerTrigger render={<Button variant="outline" />}>Set daily goal</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Daily move goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
{/* … */}
<DrawerFooter>
<DrawerClose render={<Button size="lg" />}>Save goal</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>The drawer is built on the Base UI Drawer: the panel tracks the pointer while it's dragged, dismisses when the gesture is fast or far enough, and settles on the nearest resting point instead of stopping wherever the finger let go.
Examples
Bottom sheet
The default: a drawer that rises from the bottom and is dismissed by swiping down. showSwipeHandle renders the grab handle that tells people it can be dragged. Center the content with a max-w-* wrapper so it stays readable on tablets — a share sheet is the archetype.
Snap points
snapPoints lets the drawer rest at several heights. Fractions between 0 and 1 are portions of the viewport height; numbers above 1 are pixels, and strings accept px and rem. The drawer opens at the first point; drag it up to reach the next one.
<Drawer showSwipeHandle snapPoints={[0.45, 1]}>Directions
swipeDirection sets both the edge and the dismiss gesture: down (default), up, left or right. Side drawers are 75% wide on phones and 24rem from sm up.
Nested
Open a drawer from inside another and the parent steps back into a stack — slightly smaller, slightly dimmed — so people see where they'll return. Swiping the child away brings the parent forward again.
Responsive
On desktop, a centered Dialog is easier to reach with a pointer; on phones, a bottom drawer is within thumb reach. Share the content and switch the container with useIsMobile.
const isMobile = useIsMobile()
return isMobile ? (
<Drawer showSwipeHandle>{/* … */}</Drawer>
) : (
<Dialog>{/* … */}</Dialog>
)Guidelines
When to use
- For contextual tasks on touch devices: pickers, short forms, share sheets, filters.
- When the content benefits from resting at several heights, like a map with a list of places.
When not to use
- On pointer-first desktop layouts — use a Dialog or a Sheet.
- For destructive confirmations — use an Alert Dialog, which can't be swiped away by accident.
- For long, complex forms — give them a page.
Gestures are a shortcut, never the only way
Everything a gesture does must also be possible with a tap or a key. Keep a visible close or cancel action in the footer, and don't rely on swiping between snap points to reveal essential content.
Thumb reach
Put primary actions at the bottom of the drawer, full width, in the lg size (40px tall). That's where thumbs are, and it clears the 24px minimum target comfortably.
Accessibility
The drawer is a modal dialog: it traps focus, locks page scroll, and is labeled by DrawerTitle and described by DrawerDescription.
| Key | Behavior |
|---|---|
EnterSpace | On the trigger: opens the drawer and moves focus into it. |
Tab | Moves through the drawer's controls. Focus is trapped while it's open. |
ShiftTab | Moves focus backwards. |
Esc | Closes the drawer and returns focus to the trigger. |
- Always include a title. It names the drawer for assistive technology; hide it with
sr-onlyif the design has none. - The swipe handle is decorative and hidden from assistive technology — provide a real close button.
- Motion. The drawer follows the pointer 1:1 while dragging and settles over 450ms. With reduced motion, the settle animation is removed; dragging still tracks the finger, because that motion is caused by the person, not by the interface.
API reference
Drawer
The root. Doesn't render an element. Accepts every prop of Base UI Drawer.Root.
DrawerContent
Renders the portal, scrim, viewport and panel. Accepts every prop of Base UI Drawer.Popup, including initialFocus and finalFocus.
DrawerHeader, DrawerFooter
DrawerHeader holds the title and description — centered on vertical drawers below md, left-aligned above. DrawerFooter stacks actions full width at the bottom.
DrawerTitle, DrawerDescription
DrawerTitle renders an <h2> and names the drawer. DrawerDescription renders a <p> and describes it.
DrawerTrigger, DrawerClose
Open and close the drawer. Both render a <button>; use render to render a prfct Button.