View fulldotdev/ui on GitHub

Drawer

A drawer component that slides in from any edge of the screen.

Installation

npx shadcn@latest add @fulldev/drawer

Usage

import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer swipeDirection="down">
  <DrawerTrigger>Open</DrawerTrigger>
  <DrawerContent showSwipeHandle>
    <DrawerHeader>
      <DrawerTitle>Drawer title</DrawerTitle>
      <DrawerDescription>Drawer description</DrawerDescription>
    </DrawerHeader>
    <div class="p-4">Drawer content</div>
    <DrawerFooter>
      <DrawerClose variant="outline">Close</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

DrawerTrigger and DrawerClose render a Button and take its props.

Composition

DrawerContent renders the portal, backdrop, viewport, popup and inner content wrapper. Use the lower-level exports to customize that structure.

Drawer
├── DrawerTrigger
└── DrawerContent
    ├── DrawerHeader
    │   ├── DrawerTitle
    │   └── DrawerDescription
    ├── content
    └── DrawerFooter
        └── DrawerClose

Custom Sizes

A vertical drawer sizes itself to its content, capped at calc(100dvh - 3rem). A side drawer spans 75% of the viewport width, up to 24rem. Override with h-*, max-h-*, w-* or max-w-* on DrawerContent. To make a region scrollable, make it a flex item: <div class="flex-1 overflow-y-auto p-4">.

The popup exposes data-swipe-direction, so scope styles to a side with data-[swipe-direction=down]: on DrawerContent or group-data-[swipe-direction=down]/drawer-popup: on its descendants. It does not set data-swipe-axis.

Position

Use swipeDirection on Drawer to set the side: up, right, down (default) or left.

Swipe Handle

Use showSwipeHandle on DrawerContent to render a swipe handle.

Nested

Place a complete Drawer inside another drawer’s content. Parent drawers stay mounted behind the frontmost drawer, and Escape closes only the front one.

Non Modal

Set modal={false} and showOverlay={false} on DrawerContent to keep the rest of the page interactive. Add disablePointerDismissal to keep the drawer open on outside presses. modal="trap-focus" keeps focus inside the drawer without making the page inert.

Snap Points

The official example moves between two snap points. Data Slot supports one snapPoint (a fraction of the viewport, a pixel number, or a px or rem string), so this example is in the React Drawer docs only.

Responsive

Render a Dialog from md up and a Drawer below it, with hidden md:block and md:hidden on the roots.

Events

Use the drawer:open, drawer:close, drawer:toggle and drawer:set DOM events for imperative changes. The root emits cancelable drawer:beforechange and drawer:beforesnapchange events, followed by drawer:change (with reason and originalEvent), drawer:change-complete and drawer:snapchange.

const drawer = document.querySelector('[data-slot="drawer"]')

drawer?.dispatchEvent(
  new CustomEvent("drawer:set", {
    detail: { open: true, snapPoint: "24rem" },
  })
)

API Reference

Source: src/components/ui/drawer

Drawer propTypeDefault
swipeDirection"up" | "right" | "down" | "left""down"
modalboolean | "trap-focus"true
disablePointerDismissalbooleanfalse
closeOnEscapebooleantrue
defaultOpenbooleanfalse
snapPointnumber | string | nullnull
defaultSnapPointnumber | string | nullnull
triggerIdstring | null
defaultTriggerIdstring | null
DrawerContent propTypeDefault
showOverlaybooleantrue
showSwipeHandlebooleanfalse
containerstring
keepMountedboolean
initialFocusboolean | string
finalFocusboolean | string
overlayClassstring
viewportClassstring

See the Data Slot docs for controllers, focus management, detached triggers and keyboard-aware viewports.