Drawer

A bottom sheet with native drag-to-dismiss support. Built on vaul — designed for mobile-first, touch-friendly workflows. Automatically includes a drag handle and supports velocity-based snap behaviour.

Anatomy

DrawerContent automatically renders a drag handle bar at the top. DrawerHeader centers text on mobile and left-aligns on desktop. DrawerClose wraps any element to dismiss the drawer. DrawerFooter stacks buttons vertically on mobile and rows them on desktop.

drag handle auto-renderedvaul velocity-based dismissDrawerClose to dismiss

Examples

Basic info

Action sheet

Form drawer

Drawer vs Sheet

Both are panel overlays, but they serve different interaction models.

Drawer

  • Drag-to-dismiss (vaul)
  • Mobile-first, touch-optimised
  • Bottom only (primary use case)
  • No side directions built-in

Sheet

  • All four sides (top/right/bottom/left)
  • Desktop navigation and settings panels
  • More predictable dismiss behaviour
  • No drag handle or snap behaviour

Design Guidelines

Do

  • Use for mobile-first flows and action sheets. Vaul's drag-to-dismiss matches native mobile patterns users already know.
  • Keep content lightweight. Drawers work best for focused tasks — a handful of actions or a short form.
  • Stack footer buttons vertically on mobile. DrawerFooter handles responsive stacking automatically.

Don't

  • Don't use as a desktop navigation panel. Sheet's side="left" is better suited for that — Drawer is optimised for bottom/mobile.
  • Don't fill with heavily scrollable content. Users expect drawers to be lightweight — long content should live on a dedicated page.
  • Don't rely on programmatic open alone. Always include a visible dismiss path (DrawerCloseor drag handle) so users aren't stranded.

Developer Reference

Accessibility

  • Vaul manages role="dialog", focus trapping, and ESC dismissal internally.
  • Drag handle is a visual affordance — include a DrawerClose button for keyboard and screen reader users.
  • Velocity-based dismiss: a quick downward drag closes the drawer regardless of distance travelled.
  • DrawerHeader text-aligns to center on mobile (sm:text-left on desktop).

Usage

import {
  Drawer,
  DrawerTrigger,
  DrawerContent,
  DrawerHeader,
  DrawerFooter,
  DrawerTitle,
  DrawerDescription,
  DrawerClose,
} from "@/components/ui/drawer"

// Basic drawer
<Drawer>
  <DrawerTrigger asChild>
    <Button>Open</Button>
  </DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Title</DrawerTitle>
      <DrawerDescription>Supporting description.</DrawerDescription>
    </DrawerHeader>
    <div className="px-4 pb-2">{/* content */}</div>
    <DrawerFooter>
      <Button>Confirm</Button>
      <DrawerClose asChild>
        <Button variant="outline">Cancel</Button>
      </DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

// Controlled
const [open, setOpen] = useState(false)
<Drawer open={open} onOpenChange={setOpen}>...</Drawer>