Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Drawer for React and Tailwind

A sliding panel with swipe support.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/drawer.json

Anatomy

Drawer
├── DrawerTrigger
└── DrawerContent
    ├── DrawerOverlay (built in)
    ├── DrawerPositioner (built in)
    ├── DrawerGrabber (built in)
    ├── DrawerHeader
    │   ├── DrawerTitle
    │   └── DrawerDescription
    ├── DrawerBody
    ├── DrawerFooter
    └── DrawerClose

shadcn.io wraps Ark UI Drawer. Overlay, positioner, and grabber are created by DrawerContent. useDrawer is the machine hook for DrawerRootProvider; useDrawerContext / DrawerContext is in-tree.

lazyMount and unmountOnExit default to true (Ark: false). preventScroll follows modal, and is forced off inside docs previews. Default swipe direction is down (bottom sheet).

For a desktop side panel, use Sheet. For a centered modal, use Dialog.

Usage

import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerContent,
  DrawerFooter,
  DrawerHeader,
  DrawerTrigger,
} from "@/components/ui/drawer";
<Drawer>
  <DrawerTrigger asChild>
    <Button variant="outline">Open</Button>
  </DrawerTrigger>
  <DrawerContent>
    <DrawerHeader
      description="Supporting description."
      title="Drawer title"
    />
    <DrawerBody>Content</DrawerBody>
    <DrawerFooter>
      <DrawerClose asChild>
        <Button variant="outline">Close</Button>
      </DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Controlled

Control open state with open and onOpenChange. { open: boolean }.

Root Provider

Use useDrawer with DrawerRootProvider when you need the API outside the tree. Pass machine options (snapPoints, swipeDirection, modal, …) to useDrawer(), not to the provider.

Title & Description

DrawerHeader supports two usage patterns:

Using props

Pass title and description props directly to DrawerHeader.

<DrawerHeader
  description="Do you want to allow the USB accessory to connect to this device?"
  title="Allow accessory to connect?"
/>

This approach does not require DrawerTitle or DrawerDescription components.

Using components

Use DrawerTitle and DrawerDescription as children for more control.

<DrawerHeader>
  <DrawerTitle>Allow accessory to connect?</DrawerTitle>
  <DrawerDescription>
    Do you want to allow the USB accessory to connect to this device?
  </DrawerDescription>
</DrawerHeader>

A title is required for accessibility. Use className="sr-only" on DrawerTitle when it should be visually hidden.

Examples

Inset variant

Use variant="inset" on DrawerContent so the drawer appears as a floating card rather than edge-to-edge.

Swipe directions

swipeDirection is down (default), up, start, or end. start / end follow text direction (left / right in LTR).

Snap points

Use snapPoints to snap to multiple heights. Numbers are fractions of the viewport (0.5 is 50%).

Scrollable

Long content goes in DrawerBody. preventDragOnScroll (default true) keeps scrolling from starting a swipe.

Non-modal

Use modal={false} to allow interaction with the page behind the drawer.

Close button

Pass showCloseButton to DrawerContent for a corner close control (aria-label="Close").

No drag area

Apply data-no-drag to any element inside the drawer to prevent dragging from starting there.

Non-draggable

Set draggable={false} on DrawerContent to disable drag-to-dismiss on the panel. The grabber can still drag.

Custom spacing

--space is internal padding (default --spacing(6)). --bleed is the overdrag continuation (default --spacing(12)).

<DrawerContent className="[--space:--spacing(8)]" />

Multiple triggers

Share one drawer across triggers. Pass value on each DrawerTrigger and handle onTriggerValueChange.

Indent

Wrap the page in DrawerProvider so the background scales and rounds while the drawer is open. Use modal={false} so the indented page stays visible.

Context

Read open and drag state with DrawerContext or useDrawerContext.

Guides

Drawer vs Sheet vs Dialog

DrawerSheetDialog
MotionSwipe from an edgeSlide from an edgeCentered
Typical useMobile sheet, snap pointsDesktop side panelModal task
Drag to dismissYesNoNo

Swipe direction

data-swipe-direction on content is the physical edge (up | down | left | right). swipeDirection="start" / "end" on the root map through dir.

Conditional rendering

Keep Drawer mounted and control it with open / onOpenChange. shadcn.io already sets lazyMount and unmountOnExit so portal content leaves the DOM while closed.

API Reference

shadcn.io wraps Ark UI Drawer. Defaults below are shadcn.io values. lazyMount and unmountOnExit default to true (Ark: false). preventScroll follows modal, and is forced off inside docs previews.

asChild merges props onto a single child element.

Drawer

Root. Overlay and positioner are created by DrawerContent.

PropTypeDefaultDescription
closeOnEscapebooleantrueClose when Escape is pressed.
closeOnInteractOutsidebooleantrueClose when the outside is clicked.
closeThresholdnumber0.25Fraction of size needed to dismiss on swipe.
defaultOpenbooleanfalseUncontrolled initial open state.
defaultSnapPointnumber | string | null1Uncontrolled initial snap point.
defaultTriggerValuestring-Uncontrolled initial active trigger value.
finalFocusEl() => MaybeElement-Element to focus when the drawer closes.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-closed content. activity needs React 19+.
idstring-Unique id for the machine.
idsPartial<{ backdrop: string; positioner: string; content: string; title: string; description: string; header: string; trigger: string | ((value?: string) => string); grabber: string; grabberIndicator: string; closeTrigger: string; swipeArea: string }>-Element ids for composition.
immediateboolean-Apply presence changes immediately instead of the next frame.
initialFocusEl() => MaybeElement-Element to focus when the drawer opens.
lazyMountbooleantrueMount content on first open.
modalbooleantrueTrap pointer events and hide content behind the drawer.
onEscapeKeyDown(event: KeyboardEvent) => void-Called when Escape is pressed.
onExitComplete() => void-Called when the close animation finishes.
onFocusOutside(event: FocusOutsideEvent) => void-Called when focus moves outside.
onInteractOutside(event: InteractOutsideEvent) => void-Called on outside interaction.
onOpenChange(details: OpenChangeDetails) => void-{ open: boolean }.
onPointerDownOutside(event: PointerDownOutsideEvent) => void-Called on pointer down outside.
onRequestDismiss(event: LayerDismissEvent) => void-Called when a parent layer dismisses this one.
onSnapPointChange(details: SnapPointChangeDetails) => void-Called when the snap point changes.
onTriggerValueChange(details: TriggerValueChangeDetails) => void-{ value: string | null }.
openboolean-Controlled open state.
presentboolean-Controlled presence.
preventDragOnScrollbooleantrueDo not start a drag from a scrollable child.
preventScrollbooleantrue when modalPrevent scrolling behind the drawer. Off in docs previews.
restoreFocusbooleantrueRestore focus to the previously focused element.
role"dialog" | "alertdialog""dialog"Dialog role.
skipAnimationOnMountbooleanfalseSkip the initial presence animation.
snapPointnumber | string | null-Controlled snap point.
snapPoints(number | string)[][1]Snap points. Numbers are viewport fractions.
snapToSequentialPointsbooleanfalseSnap only to the next point when swiping.
stackDrawerStack-External stack store for indent visuals.
swipeDirection"up" | "down" | "start" | "end""down"Edge the drawer slides from. start / end follow dir.
swipeVelocityThresholdnumber700Velocity in px/s that dismisses the drawer.
trapFocusbooleantrueTrap focus inside the drawer.
triggerValuestring-Controlled active trigger value.
unmountOnExitbooleantrueUnmount content after the close animation.
AttributeDescription
data-slotdrawer
data-scopedrawer
data-partroot

DrawerTrigger

Opens the drawer. Renders a button.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the trigger.
valuestring-Id for this trigger when several share one drawer.
AttributeDescription
data-slotdrawer-trigger
data-scopedrawer
data-parttrigger
data-valueThe trigger value
data-state"open" or "closed"
data-currentPresent when this trigger is the active one

DrawerContent

Portaled panel. Includes overlay, positioner, and grabber. Renders a div.

PropTypeDefaultDescription
variant"default" | "inset""default"Edge-to-edge, or padded floating card.
showBarbooleantrueShow the grabber on top and bottom drawers.
showCloseButtonbooleanfalseShow a corner close button (aria-label="Close").
draggablebooleantrueIf false, only the grabber can drag.
asChildbooleanfalseMerge onto a single child of the panel.
classNamestring-Class names on the panel.
AttributeDescription
data-slotdrawer-content
data-scopedrawer
data-partcontent
data-state"open" or "closed"
data-swipe-direction"up", "down", "left", or "right"
data-swipingPresent while swiping
data-draggingPresent while dragging
data-expandedPresent when expanded
data-nested-drawer-openPresent when a nested drawer is open
CSS variableDefaultDescription
--space--spacing(6)Internal padding.
--bleed--spacing(12)Overdrag continuation beyond the panel.
--drawer-heightmeasuredHeight of the panel.
--drawer-translate-x / --drawer-translate-y0Drag translation.
--layer-indexstackDismissable layer index.

DrawerHeader

Header for title and description. Pass title / description or compose DrawerTitle and DrawerDescription.

PropTypeDefaultDescription
titlestring-Renders DrawerTitle.
descriptionstring-Renders DrawerDescription.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the header.
AttributeDescription
data-slotdrawer-header

DrawerTitle

Accessible title. Renders an h2.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the title.
AttributeDescription
data-slotdrawer-title
data-scopedrawer
data-parttitle

DrawerDescription

Accessible description. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the description.
AttributeDescription
data-slotdrawer-description
data-scopedrawer
data-partdescription

DrawerBody

Scrollable body. Uses Scroll Area.

PropTypeDefaultDescription
scrollFadebooleanfalseFade at the scroll edges.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the body.
AttributeDescription
data-slotdrawer-body

DrawerFooter

Footer for actions. Renders a div.

PropTypeDefaultDescription
variant"default" | "bare""default"default has a top border and muted background.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the footer.
AttributeDescription
data-slotdrawer-footer

DrawerClose

Closes the drawer. Renders a button.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the close control.
AttributeDescription
data-slotdrawer-close
data-scopedrawer
data-partclose-trigger

DrawerGrabber

Drag handle. Created by DrawerContent for up / down drawers. Hidden on side drawers and while a nested drawer is open.

PropTypeDefaultDescription
showbooleantrueRender the grabber.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the grabber.
AttributeDescription
data-slotdrawer-grabber
data-scopedrawer
data-partgrabber

DrawerProvider

App-level indent wrapper (Drawer.Stack + background + indent). Wrap the page (or a preview) so content scales while a drawer is open.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the indent surface.
AttributeDescription
data-slotdrawer-indent

DrawerRootProvider

Root alternative that takes the API from useDrawer. Presence (lazyMount, unmountOnExit) can still be set on the provider. Pass modal, snapPoints, and swipeDirection to useDrawer().

PropTypeDefaultDescription
valueUseDrawerReturnrequiredReturn value of useDrawer().
lazyMountbooleantrueMount content on first open.
unmountOnExitbooleantrueUnmount after the close animation.
AttributeDescription
data-slotdrawer

useDrawer

Creates the drawer API for DrawerRootProvider. Same options as Drawer except layout-only props.

const drawer = useDrawer({
  snapPoints: [0.25, 0.5, 1],
  defaultSnapPoint: 0.5,
});

drawer.setOpen(true);
drawer.setSnapPoint(1);

DrawerContext / useDrawerContext

Render-prop or hook access to drawer state. Use inside Drawer or DrawerRootProvider.

PropertyTypeDescription
openbooleanWhether the drawer is open.
setOpen(open: boolean) => voidOpen or close.
draggingbooleanWhether the drawer is being dragged.
triggerValuestring | nullActive trigger value.
setTriggerValue(value: string | null) => voidSet the active trigger.
snapPoints(number | string)[]Configured snap points.
snapPointnumber | string | nullActive snap point.
setSnapPoint(snapPoint: number | string | null) => voidSet the snap point.
swipeDirection"up" | "down" | "start" | "end"Configured swipe direction.
getOpenPercentage() => numberOpen amount from 0 to 1.
getSnapPointIndex() => numberIndex of the active snap point.
getContentSize() => number | nullMain-axis size of the panel.

DrawerContext children: (context) => ReactNode.

Accessibility

Complies with the Dialog WAI-ARIA design pattern. Always include DrawerTitle (or title on DrawerHeader). Use className="sr-only" when the title should be visually hidden.

Keyboard support

KeyDescription
Enter / SpaceWhen focus is on the trigger, opens the drawer.
TabMoves focus to the next focusable element. Focus is trapped while open.
Shift + TabMoves focus to the previous focusable element.
EscapeCloses the drawer and returns focus to the trigger (or finalFocusEl).