Drawer

Drawer

A swipe-dismissable panel that slides in from any screen edge, built on Base UI. Supports snap points, a grab handle and nested stacking.

Playground

swipeDirection
showSwipeHandle
snapPoints
<Drawer showSwipeHandle>
  <DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Drawer title</DrawerTitle>
      <DrawerDescription>
        A short description of the drawer contents.
      </DrawerDescription>
    </DrawerHeader>
    <DrawerFooter className="pt-4">
      <DrawerClose render={<Button variant="outline">Close</Button>} />
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Preview

The default drawer slides up from the bottom (swipeDirection="down" means it dismisses downward). Compose it from DrawerHeader, free-form content and a DrawerFooter pinned to the end.

<Drawer>
  <DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Drawer title</DrawerTitle>
      <DrawerDescription>
        A short description of the drawer contents.
      </DrawerDescription>
    </DrawerHeader>
    <div className="p-4 text-sm text-muted-foreground">
      Drag the drawer down or press the close button to dismiss.
    </div>
    <DrawerFooter>
      <Button>Submit</Button>
      <DrawerClose render={<Button variant="outline">Cancel</Button>} />
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Installation

npx shadcn@latest add https://espresso-base-ui.vercel.app/r/drawer.json

Usage

import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerOverlay,
  DrawerPortal,
  DrawerSwipeHandle,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer>
  <DrawerTrigger render={<Button variant="outline">Open</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Title</DrawerTitle>
      <DrawerDescription>Description</DrawerDescription>
    </DrawerHeader>
    <DrawerFooter>
      <DrawerClose render={<Button variant="outline">Close</Button>} />
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Directions

swipeDirection sets the edge the drawer rests on and the direction it swipes away: down (default, bottom sheet), up (top sheet), left and right (side panels, 75% wide on mobile and 24rem on desktop).

<Drawer swipeDirection="down" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="up" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="left" showSwipeHandle>...</Drawer>
<Drawer swipeDirection="right" showSwipeHandle>...</Drawer>

Swipe handle

showSwipeHandle renders a small grab bar on the swipe edge that hints the drawer is draggable – it orients itself automatically to the active direction.

<Drawer showSwipeHandle>
  <DrawerTrigger render={<Button variant="outline">Open with handle</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Grab and drag</DrawerTitle>
      <DrawerDescription>
        The handle at the top hints that the drawer is draggable.
      </DrawerDescription>
    </DrawerHeader>
    <DrawerFooter>
      <DrawerClose render={<Button variant="outline">Close</Button>} />
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Snap points

snapPoints lets a vertical drawer rest at intermediate heights – pass fractions of the viewport and drag between them; the overlay keeps a minimum opacity while snapped.

<Drawer snapPoints={[0.4, 1]} showSwipeHandle>
  <DrawerTrigger render={<Button variant="outline">Open with snap points</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Snap points</DrawerTitle>
      <DrawerDescription>
        Drag to snap between 40% and full height.
      </DrawerDescription>
    </DrawerHeader>
    <div className="flex flex-col gap-4 overflow-y-auto p-4">...</div>
  </DrawerContent>
</Drawer>

API reference

Drawer wraps the Base UI Drawer root and shares modal, showSwipeHandle, snapPoints and swipeDirection with DrawerContent via context. It also accepts the Base UI root props – open, defaultOpen, onOpenChange and dismissible. Interactive parts (DrawerTrigger, DrawerClose) support the Base UI render prop to swap in your own element; pass nativeButton={false} if it is not a native button.

Drawer

PropTypeDefaultDescription
swipeDirection"down" | "up" | "left" | "right""down"Edge the drawer rests on and the direction it swipes away toward.
showSwipeHandlebooleanfalseRender a grab bar (DrawerSwipeHandle) on the swipe edge of the popup.
snapPoints(number | string)[]–Intermediate resting heights for vertical drawers, e.g. [0.4, 1].
modalbooleantrueRender the dimmed overlay and block interaction with the page behind; set false for a non-modal drawer.
ComponentDescription
DrawerTriggerOpens the drawer (data-slot="drawer-trigger"); compose your button via the render prop.
DrawerContentRenders the portal, overlay, viewport and the sliding popup (data-slot="drawer-popup") that wraps your content (data-slot="drawer-content").
DrawerOverlayBackdrop (data-slot="drawer-overlay") whose opacity tracks the swipe progress; rendered automatically when modal.
DrawerSwipeHandleDecorative grab bar (data-slot="drawer-swipe-handle", aria-hidden) that follows the swipe axis and direction.
DrawerHeaderTitle/description stack (data-slot="drawer-header"); centered for vertical drawers, left-aligned on desktop.
DrawerTitleAccessible heading (data-slot="drawer-title") announced when the drawer opens.
DrawerDescriptionMuted supporting copy (data-slot="drawer-description").
DrawerFooterAction stack pinned to the end (data-slot="drawer-footer").
DrawerCloseDismisses the drawer (data-slot="drawer-close"); compose any button via the render prop.
DrawerPortalPortals drawer parts to the document body (data-slot="drawer-portal"); used internally by DrawerContent.

Accessibility & styling hooks

Base UI renders the drawer as a modal dialog – focus is trapped while open, Escape and backdrop click dismiss it, and DrawerTitle / DrawerDescription label it for screen readers. Every part exposes a data-slot attribute, and the popup reflects gesture state as data-swipe-direction, data-swipe-axis, data-snap-points, data-swiping and data-nested-drawer-open. CSS variables such as --drawer-swipe-progress, --drawer-height and --drawer-inset are available for advanced styling – the overlay, bleed and stacking effects are all driven by them.

Search documentation

Search the component docs