1
ComponentsOverlays

Drawer

Drawer provides swipeable edge panels for mobile menus, contextual details, and gesture-driven flows.

Purpose

Drawer provides swipeable edge panels for mobile menus, contextual details, and gesture-driven flows. Use Sheet for dialog-style form and save/cancel flows.

Context drawer

import { Drawer, DrawerPopup, DrawerTrigger } from "@/components/ui/drawer";
import { Button } from "@/components/ui/button";

export function Example() {
  return (
    <Drawer>
      <DrawerTrigger render={<Button variant="outline" />}>Open details</DrawerTrigger>
      <DrawerPopup>...</DrawerPopup>
    </Drawer>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/drawer

Import

import { Drawer, DrawerCreateHandle, DrawerPanel, DrawerPopup, DrawerSwipeArea, DrawerTrigger } from "@/components/ui/drawer";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Drawer for contextual panels and mobile edge interactions. Use Sheet for direct edit forms.

import { Drawer, DrawerPopup, DrawerTrigger } from "@/components/ui/drawer";
import { Button } from "@/components/ui/button";

export function Example() {
  return (
    <Drawer>
      <DrawerTrigger render={<Button variant="outline" />}>Open details</DrawerTrigger>
      <DrawerPopup>...</DrawerPopup>
    </Drawer>
  );
}

Examples

Positions, popup variants, drag affordances, snap points, nesting and the drawer menu system are props and compositions of the same primitive.

Positions

The position prop attaches the drawer to any viewport edge. The swipe direction follows automatically.

{/* right | left | top | bottom, default bottom */}
<Drawer position="right">
  <DrawerTrigger render={<Button variant="outline" />}>Right</DrawerTrigger>
  <DrawerPopup>
    <DrawerHeader>
      <DrawerTitle>Right</DrawerTitle>
      <DrawerDescription>Drawer attached to the right edge.</DrawerDescription>
    </DrawerHeader>
  </DrawerPopup>
</Drawer>

Close button and bar

Both affordances are opt-in: showCloseButton renders a corner close action, showBar renders the drag handle.

{/* Corner close button, opt-in */}
<DrawerPopup showCloseButton>...</DrawerPopup>

{/* Drag bar on the swipe edge, opt-in */}
<DrawerPopup showBar>...</DrawerPopup>

Scrollable content

DrawerPanel wraps its content in a ScrollArea, so header and footer stay visible.

<DrawerPopup>
  <DrawerHeader>
    <DrawerTitle>Scrollable content</DrawerTitle>
  </DrawerHeader>
  <DrawerPanel>
    <div className="flex flex-col gap-2">
      {items.map((key) => (
        <div className="h-12 shrink-0 rounded-lg border bg-muted" key={key} />
      ))}
    </div>
  </DrawerPanel>
  <DrawerFooter>
    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
  </DrawerFooter>
</DrawerPopup>

Snap points

snapPoints, snapPoint and onSnapPointChange pass through to the drawer root.

const snapPoints = ["300px", 1] as const;
const [snapPoint, setSnapPoint] = useState<(typeof snapPoints)[number] | null>(
  snapPoints[0],
);

<Drawer
  onSnapPointChange={(point) =>
    setSnapPoint(point as (typeof snapPoints)[number] | null)
  }
  snapPoint={snapPoint}
  snapPoints={[...snapPoints]}
  snapToSequentialPoints
>
  <DrawerTrigger render={<Button variant="outline" />}>
    With snap points
  </DrawerTrigger>
  <DrawerPopup>...</DrawerPopup>
</Drawer>

Nested drawers

A drawer inside an open drawer keeps the native stack and swipe animation.

<Drawer>
  <DrawerTrigger render={<Button variant="outline" />}>Nested drawers</DrawerTrigger>
  <DrawerPopup>
    <DrawerHeader className="text-center">
      <DrawerTitle>First step</DrawerTitle>
    </DrawerHeader>
    <DrawerFooter className="justify-center sm:justify-center" variant="bare">
      <DrawerClose render={<Button variant="ghost" />}>Cancel</DrawerClose>
      <Drawer>
        <DrawerTrigger render={<Button variant="outline" />}>Continue</DrawerTrigger>
        <DrawerPopup>
          <DrawerHeader className="text-center">
            <DrawerTitle>Second step</DrawerTitle>
          </DrawerHeader>
        </DrawerPopup>
      </Drawer>
    </DrawerFooter>
  </DrawerPopup>
</Drawer>

Mobile menu

A left drawer with the drawer menu components. Each item closes the drawer on activation.

<Drawer position="left">
  <DrawerTrigger render={<Button variant="outline" />}>
    <MenuIcon />
    Open menu
  </DrawerTrigger>
  <DrawerPopup showCloseButton>
    <DrawerHeader>
      <DrawerTitle>Menu</DrawerTitle>
    </DrawerHeader>
    <DrawerPanel>
      <DrawerMenu>
        <DrawerMenuGroup>
          <DrawerMenuGroupLabel>Navigation</DrawerMenuGroupLabel>
          <DrawerClose render={<DrawerMenuItem />}>Home</DrawerClose>
          <DrawerClose render={<DrawerMenuItem />}>Profile</DrawerClose>
          <DrawerClose render={<DrawerMenuItem />}>Settings</DrawerClose>
        </DrawerMenuGroup>
        <DrawerMenuSeparator />
        <DrawerClose render={<DrawerMenuItem variant="destructive" />}>
          Sign out
        </DrawerClose>
      </DrawerMenu>
    </DrawerPanel>
  </DrawerPopup>
</Drawer>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • position places the Drawer at an edge. swipeDirection normally follows that position but may override the gesture direction.
  • DrawerSwipeArea position creates a fixed edge target that can open the Drawer with a swipe.
  • DrawerCreateHandle supports detached triggers and imperative opening.
  • DrawerPanel scrollable={false} removes the ScrollArea wrapper; scrollFade={false} keeps scroll edges hard instead of faded.
  • allowSelection on Header, Footer, and Panel chooses selectable content behavior instead of a drag surface.
  • Drawer menu items, checkbox items, and radio items accept disabled.
  • variant="straight" and inset variants define the native surface shape; do not recreate them with local classes.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Popup, backdrop, stacking, snap points, and swipe progress are native Drawer behavior.
  • Do not copy Sheet motion onto Drawer or wrap the popup in another transform.
  • Interactive triggers and menu items use a pointer cursor. Drag and selectable content surfaces keep their appropriate default or text cursor.

Accessibility

  • Give every Drawer a DrawerTitle and DrawerDescription, visually hidden when the visible design already supplies equivalent context.
  • Preserve Escape, focus restoration, and modal behavior where configured.
  • Disabled menu items must be unavailable and visibly distinct.
  • Keep text selection enabled for content users may need to copy.

Implementation Guidance

  • Use Drawer for edge and gesture flows; use Sheet for form-oriented flyouts.
  • Choose position, swipe area, scroll, selection, and disabled props through the public API instead of local overrides.
  • Preserve native gesture, stack, focus, cursor, and motion behavior.

On This Page