# Drawer

A swipeable sheet built on Base UI Drawer that slides in from any edge, with snap points, a drag handle, and accessible title and description primitives.

- Category: Overlays & Navigation
- License: MIT
- Page: https://www.saasuji.com/components/drawer
- Install: npx shadcn@latest add https://www.saasuji.com/r/drawer.json

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| open (Drawer) | `boolean` | — | Controlled open state of the drawer. |
| defaultOpen (Drawer) | `boolean` | `false` | Open state on first render for an uncontrolled drawer. |
| onOpenChange (Drawer) | `(open: boolean, eventDetails: DrawerRoot.ChangeEventDetails) => void` | — | Called when the drawer is opened or closed. |
| onOpenChangeComplete (Drawer) | `(open: boolean) => void` | — | Called after the open or close animation finishes. |
| modal (Drawer) | `boolean | 'trap-focus'` | `true` | Locks page scroll and traps focus; 'trap-focus' traps focus without locking scroll. |
| disablePointerDismissal (Drawer) | `boolean` | `false` | Prevents the drawer from closing on outside presses. |
| swipeDirection (Drawer) | `'up' | 'down' | 'left' | 'right'` | `'down'` | Edge the drawer slides from and the direction that dismisses it. |
| snapPoints (Drawer) | `DrawerSnapPoint[]` | — | Snap positions as viewport fractions, pixel numbers, or px/rem strings. |
| snapToSequentialPoints (Drawer) | `boolean` | `false` | Uses drag distance instead of velocity to pick the next snap point. |
| snapPoint (Drawer) | `DrawerSnapPoint | null` | — | Controlled active snap point. |
| defaultSnapPoint (Drawer) | `DrawerSnapPoint | null` | — | Initial snap point for an uncontrolled drawer. |
| onSnapPointChange (Drawer) | `(snapPoint: DrawerSnapPoint | null, eventDetails: DrawerRoot.SnapPointChangeEventDetails) => void` | — | Called when the active snap point changes. |
| handle (Drawer, DrawerTrigger) | `DrawerHandle` | — | Associates a trigger with the drawer for detached trigger control. |
| actionsRef (Drawer) | `React.RefObject<DrawerRoot.Actions | null>` | — | Imperative actions exposing close and unmount. |
| render (DrawerTrigger, DrawerClose, DrawerCloseButton) | `React.ReactElement | ((props, state) => React.ReactElement)` | — | Renders a custom element in place of the default button. |
| className (all parts) | `string` | — | Additional classes merged onto the part that receives it. |
| children (all parts) | `React.ReactNode` | — | Handle, header with title/description, body content, and footer actions inside DrawerContent. |
| ...props (Drawer) | `DrawerPrimitive.Root.Props` | — | All remaining Base UI Drawer root props are forwarded. |
| ...props (DrawerTrigger, DrawerClose, DrawerCloseButton) | `DrawerPrimitive.Trigger.Props | DrawerPrimitive.Close.Props` | — | All remaining Base UI trigger and close props are forwarded (payload, nativeButton, …). |
| ...props (DrawerPortal, DrawerOverlay, DrawerContent) | `DrawerPrimitive.Portal.Props | DrawerPrimitive.Backdrop.Props | DrawerPrimitive.Popup.Props` | — | All remaining Base UI portal, backdrop, and popup props are forwarded. |
| ...props (DrawerTitle, DrawerDescription) | `DrawerPrimitive.Title.Props | DrawerPrimitive.Description.Props` | — | All remaining Base UI title and description props are forwarded. |
| ...props (DrawerSwipeHandle, DrawerHeader, DrawerFooter) | `React.ComponentProps<"div">` | — | All remaining native div attributes are forwarded. |

## Source

```tsx
"use client"

import * as React from "react"
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer"
import X from "lucide-react/dist/esm/icons/x"

import { cn } from "@/lib/utils"

function Drawer({ ...props }: DrawerPrimitive.Root.Props) {
  return <DrawerPrimitive.Root data-slot="drawer" {...props} />
}

function DrawerTrigger({ ...props }: DrawerPrimitive.Trigger.Props) {
  return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}

function DrawerPortal({ ...props }: DrawerPrimitive.Portal.Props) {
  return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}

function DrawerClose({ ...props }: DrawerPrimitive.Close.Props) {
  return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}

function DrawerOverlay({
  className,
  ...props
}: DrawerPrimitive.Backdrop.Props) {
  return (
    <DrawerPrimitive.Backdrop
      data-slot="drawer-overlay"
      className={cn(
        "fixed inset-0 z-[1200] bg-black/50 transition-opacity duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0",
        className
      )}
      {...props}
    />
  )
}

function DrawerContent({
  className,
  children,
  ...props
}: DrawerPrimitive.Popup.Props) {
  return (
    <DrawerPortal>
      <DrawerOverlay />
      <DrawerPrimitive.Viewport className="pointer-events-none fixed inset-0 z-[1250]">
        <DrawerPrimitive.Popup
          data-slot="drawer-content"
          className={cn(
            "group/drawer-popup pointer-events-auto fixed flex flex-col bg-popover text-popover-foreground outline-none transition-transform duration-300 ease-out data-[swiping]:transition-none",
            "data-[swipe-direction=down]:inset-x-0 data-[swipe-direction=down]:bottom-0 data-[swipe-direction=down]:max-h-[calc(100dvh-6rem)] data-[swipe-direction=down]:rounded-t-2xl data-[swipe-direction=down]:border-t data-[swipe-direction=down]:data-ending-style:translate-y-full data-[swipe-direction=down]:data-starting-style:translate-y-full",
            "data-[swipe-direction=up]:inset-x-0 data-[swipe-direction=up]:top-0 data-[swipe-direction=up]:max-h-[calc(100dvh-6rem)] data-[swipe-direction=up]:rounded-b-2xl data-[swipe-direction=up]:border-b data-[swipe-direction=up]:data-ending-style:-translate-y-full data-[swipe-direction=up]:data-starting-style:-translate-y-full",
            "data-[swipe-direction=left]:top-0 data-[swipe-direction=left]:bottom-0 data-[swipe-direction=left]:left-0 data-[swipe-direction=left]:w-3/4 data-[swipe-direction=left]:max-w-sm data-[swipe-direction=left]:border-r data-[swipe-direction=left]:data-ending-style:-translate-x-full data-[swipe-direction=left]:data-starting-style:-translate-x-full",
            "data-[swipe-direction=right]:top-0 data-[swipe-direction=right]:right-0 data-[swipe-direction=right]:bottom-0 data-[swipe-direction=right]:w-3/4 data-[swipe-direction=right]:max-w-sm data-[swipe-direction=right]:border-l data-[swipe-direction=right]:data-ending-style:translate-x-full data-[swipe-direction=right]:data-starting-style:translate-x-full",
            className
          )}
          {...props}
        >
          {children}
        </DrawerPrimitive.Popup>
      </DrawerPrimitive.Viewport>
    </DrawerPortal>
  )
}

function DrawerSwipeHandle({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="drawer-swipe-handle"
      className={cn("mx-auto mt-2 h-1.5 w-12 shrink-0 rounded-full bg-border", className)}
      {...props}
    />
  )
}

function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="drawer-header"
      className={cn("flex flex-col gap-1.5 p-4", className)}
      {...props}
    />
  )
}

function DrawerTitle({
  className,
  ...props
}: DrawerPrimitive.Title.Props) {
  return (
    <DrawerPrimitive.Title
      data-slot="drawer-title"
      className={cn("font-semibold tracking-tight", className)}
      {...props}
    />
  )
}

function DrawerDescription({
  className,
  ...props
}: DrawerPrimitive.Description.Props) {
  return (
    <DrawerPrimitive.Description
      data-slot="drawer-description"
      className={cn("text-sm text-muted-foreground", className)}
      {...props}
    />
  )
}

function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="drawer-footer"
      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
      {...props}
    />
  )
}

function DrawerCloseButton({
  className,
  ...props
}: DrawerPrimitive.Close.Props) {
  return (
    <DrawerPrimitive.Close
      data-slot="drawer-close-button"
      className={cn(
        "absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:outline-hidden disabled:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
        className
      )}
      {...props}
    >
      <X />
      <span className="sr-only">Close</span>
    </DrawerPrimitive.Close>
  )
}

export {
  Drawer,
  DrawerPortal,
  DrawerOverlay,
  DrawerTrigger,
  DrawerClose,
  DrawerContent,
  DrawerHeader,
  DrawerFooter,
  DrawerTitle,
  DrawerDescription,
  DrawerSwipeHandle,
  DrawerCloseButton,
}

```
