Drawer

A drawer component for React.

Installation

pnpm dlx cubix@latest add drawer

Add the following to your global styles. On iOS Safari, the drawer overlay is absolutely positioned and requires a positioned body to cover the viewport after the page is scrolled. See the Base UI docs for details.

app/globals.css
body {
  position: relative;
}

Usage

Import
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/cubix/drawer"
Example
<Drawer>
  <DrawerTrigger render={<Button variant="outline" />}>Open</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Are you absolutely sure?</DrawerTitle>
      <DrawerDescription>This action cannot be undone.</DrawerDescription>
    </DrawerHeader>
    <div className="p-4">{/* Content here */}</div>
    <DrawerFooter>
      <Button>Submit</Button>
      <DrawerClose render={<Button variant="outline" />}>Cancel</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Composition

Use the following composition to build a Drawer:

 
Drawer
├── DrawerTrigger
└── DrawerContent
    ├── DrawerHeader
       ├── DrawerTitle
       └── DrawerDescription
    └── DrawerFooter

DrawerContent composes the portal, overlay, viewport, and popup from Base UI. For lower-level control, DrawerPortal, DrawerOverlay, and DrawerSwipeHandle are also exported.

Custom Sizes

A vertical drawer sizes itself to its content and is capped at calc(100dvh - 6rem) by default. A side drawer spans 75% of the viewport width, or 24rem on larger screens.

To customize the height of a vertical drawer, use the h-* and max-h-* utilities on DrawerContent.

 
<DrawerContent className="h-[50vh]">

To customize the width of a side drawer, use the w-* and max-w-* utilities on DrawerContent.

 
<DrawerContent className="w-96">

When the same component renders in multiple directions, scope an override to one axis using the data-[swipe-axis=*] variants.

 
<DrawerContent className="data-[swipe-axis=y]:max-h-[50vh] data-[swipe-axis=x]:w-96">

To make a region of the drawer scrollable, make the scroll container a flex item. Avoid h-full, which does not resolve inside a content-sized drawer.

 
<DrawerContent>
  <DrawerHeader>...</DrawerHeader>
  <div className="flex-1 overflow-y-auto p-4">{/* Scrollable content */}</div>
  <DrawerFooter>...</DrawerFooter>
</DrawerContent>

Layouts

Compose drawers with a header, footer, both, or edge-to-edge content.

Styling

The drawer exposes CSS variables for style-level customization. Set the sizing variables on DrawerContent. Set the overlay variable on [data-slot=drawer-overlay] in your CSS.

VariableDefaultDescription
--drawer-inset--spacing(2)Floats the drawer from the viewport edges.
--drawer-bleed-backgroundvar(--color-popover)Fills the gap behind the drawer on swipe overshoot.
--drawer-overlay-min-opacity0Minimum overlay opacity. Defaults to 0.5 when snap points are active.

The drawer also sets data attributes you can target with variants such as data-[swipe-direction=down]: on DrawerContent, or group-data-[swipe-axis=y]/drawer-popup: on its descendants.

AttributeValuesSet when
data-swipe-directionup, right, down, leftAlways.
data-swipe-axisx, yAlways.
data-snap-pointsPresentThe drawer has snap points.
data-expandedPresentThe drawer is at the full snap point.
data-swipingPresentA swipe is in progress.
data-nested-drawer-openPresentA nested drawer is open on top.

Position

Use the swipeDirection prop to set the side of the drawer.

Available options are up, right, down, and left.

Swipe Handle

Use showSwipeHandle on Drawer to render a swipe handle.

Nested

Open drawers from inside another drawer. Parent drawers stay mounted and stack behind the frontmost drawer.

Non Modal

Set modal={false} to allow interaction with the rest of the page while the drawer is open. Combine with disablePointerDismissal to prevent the drawer from closing on outside presses. Use modal="trap-focus" to keep focus inside the drawer while leaving scroll and pointer interaction unrestricted.

Snap Points

Use snapPoints to snap a drawer to preset heights. Numbers between 0 and 1 represent fractions of the viewport. Numbers greater than 1 are treated as pixel values. String values support px and rem units. Snap points apply to vertical drawers.

Track the active snap point with the controlled snapPoint and onSnapPointChange props. At the full snap point, the drawer gets a data-expanded attribute you can style with the data-expanded: variant.

Responsive

You can combine the Dialog and Drawer components to create a responsive dialog. This renders a Dialog component on desktop and a Drawer on mobile.

API Reference

See the Base UI documentation for the full API reference.

Drawer

PropTypeDefaultDescription
swipeDirection"up" | "right" | "down" | "left""down"Edge of the screen the drawer opens from.
showSwipeHandlebooleanfalseRender a swipe handle inside DrawerContent.
modalboolean | "trap-focus"trueWhen false, allow interacting with the rest of the page. Use trap-focus to keep focus inside without blocking pointer events.
disablePointerDismissalboolean-Prevent closing when pressing outside the drawer.
snapPoints(number | string)[]-Preset heights for vertical drawers. Values between 0 and 1 are viewport fractions; larger numbers are pixels; strings support px and rem.
snapPointnumber | string | null-Controlled active snap point.
onSnapPointChange(snapPoint: number | string | null) => void-Called when the active snap point changes.
childrenReact.ReactNode-Trigger and content elements for the drawer.

DrawerTrigger / DrawerClose

PropTypeDefaultDescription
renderReact.ReactElement-Render the trigger or close control as another element (e.g. a Button).
classNamestring-Additional Tailwind classes merged with the component styles (last one wins).

DrawerContent

PropTypeDefaultDescription
classNamestring-Additional Tailwind classes merged with the component styles. Use h-*, max-h-*, w-*, or max-w-* to size the drawer.

DrawerHeader / DrawerFooter / DrawerTitle / DrawerDescription

PropTypeDefaultDescription
classNamestring-Additional Tailwind classes merged with the component styles (last one wins).