Components

Drawer

A panel that slides in from any edge of the screen, built on Vaul with smooth drag-to-dismiss gestures and a dimmed overlay.

Basic usage

Compose a DrawerTrigger with DrawerContent. State is managed internally — no useState required. Unlike Sheet there is no corner ✕, so always leave a DrawerClose in the footer.

Direction

Set direction on the root to slide in from any edge. Vaul stamps the choice onto the panel as data-vaul-drawer-direction, and the layout follows from there: side drawers cap at sm:max-w-sm, top and bottom at max-h-[80vh] with the rounded edge facing the screen.

Grab handle

Bottom drawers get a grab handle at the top of the panel; the other three directions do not render one. It is painted with bg-control-edge rather than bg-muted on purpose: the bar is the only sign that says “drag here”, so it owes the 3:1 of WCAG 1.4.11 rather than the looser bar for a surface. Against the panel background bg-muted measures 1.07:1 in light and 1.17:1 in dark — effectively invisible — while bg-control-edge lands at 3.83:1 and 4.94:1.

Scrollable content

The header and footer stay pinned while a flex-grown body scrolls. Wrap long content in an overflow-y-auto region.

Drawer or Sheet?

Drawer is built on vaul: the panel follows the finger, carries momentum, and can be flicked away — the right feel on a touch screen. Sheet is a Radix Dialog that slides on a fixed track with a corner close button, which suits desktop filters and detail panels. Reach for Drawer when the gesture matters, Sheet when it does not.

Props

Props below are set on the root Drawer. All subcomponents forward their native and Vaul props.

PropTypeDefaultDescription
direction'top' | 'bottom' | 'left' | 'right''bottom'Edge the drawer slides in from. Set on the root Drawer — vaul writes it onto the content as data-vaul-drawer-direction, which is what the styling keys off.
openbooleanControls the open state. Omit for an uncontrolled drawer managed internally.
defaultOpenbooleanfalseOpen on mount, skipping the enter animation. Still reacts to later open changes.
onOpenChange(open: boolean) => voidCalled when the drawer is requested to open or close.
modalbooleantrueWhen true, interaction with outside elements is blocked while open.
dismissiblebooleantrueWhen false, dragging, clicking outside and Escape all stop closing the drawer — pair it with a controlled open prop or there is no way out.
closeThresholdnumber0.25Fraction of the drawer that has to be dragged away before releasing closes it.
shouldScaleBackgroundbooleanfalseScales the page behind the drawer for the iOS-style stacked effect. Requires a [vaul-drawer-wrapper] element around the page content.
snapPoints(number | string)[]Optional snap positions for partially-open states, from least to most visible — e.g. [0.4, 0.8, 1] or px strings.
fadeFromIndexnumberlast snap pointIndex of the snap point from which the overlay starts to fade. Only valid alongside snapPoints.
activeSnapPointnumber | string | nullControlled active snap point; pair with setActiveSnapPoint.
setActiveSnapPoint(snapPoint: number | string | null) => voidSetter for the controlled activeSnapPoint — vaul calls it when the user drags between snap points.
handleOnlybooleanfalseRestricts dragging to a vaul <Drawer.Handle />. The package draws its own handle as a plain div and does not re-export Handle, so turning this on leaves nothing draggable — leave it off.
autoFocusbooleanfalseFocus the drawer content when it opens.
nestedbooleanfalseMarks a drawer opened from inside another drawer so the two stack correctly.
childrenrequiredReact.ReactNodeTrigger and content subcomponents.

Parts

Every part accepts className, which is merged onto the defaults with cn().

PropTypeDefaultDescription
Drawervaul Drawer.RootStateful container. Every prop in the table above is set here.
DrawerTriggervaul Drawer.TriggerElement that opens the drawer. Use asChild to render your own Button.
DrawerPortalvaul Drawer.PortalPortal target. DrawerContent renders one already — reach for it only when building a custom panel shell.
DrawerOverlayvaul Drawer.OverlayDimmed backdrop (bg-ink/60, dark:bg-ink/80, backdrop-blur). Also rendered for you by DrawerContent.
DrawerContentvaul Drawer.ContentThe panel: portal + overlay + a flex column, plus the grab handle on bottom drawers. It has no built-in close button — supply a DrawerClose yourself.
DrawerHeaderdivTitle region, p-4. Text is centred for top and bottom drawers and left-aligned from md up.
DrawerTitlevaul Drawer.TitleAccessible name for the panel. Required for screen-reader announcements.
DrawerDescriptionvaul Drawer.DescriptionSupporting text linked to the panel via aria-describedby.
DrawerFooterdivActions region: mt-auto pins it to the base of the panel, p-4 matches the header.
DrawerClosevaul Drawer.CloseDismisses the drawer. Use asChild to wrap a custom Button.

Accessibility

  • Focus is trapped inside the drawer while open and restored to the trigger on close.
  • The overlay and surrounding page are marked inert in modal mode, so screen readers stay scoped to the drawer.
  • Always include a DrawerTitle — it labels the dialog for assistive tech; pair it with DrawerDescription for context.
  • Pressing Esc or clicking the overlay dismisses the drawer unless dismissible is false.
  • The grab handle is decorative and not focusable — dragging is a pointer gesture only, so a DrawerClose button is the keyboard route out and is never optional.