Skip to content
WANCRAFT

B.160Cards & Containers

Side Drawer

A left or right panel on a native dialog: modal overlay or non-modal push, with velocity drag-to-close.

NewReactMotionTailwindadvanced

Overview

Side Drawer is a side panel built on a native <dialog>. In overlay mode it opens with showModal, so it lives in the top layer with a backdrop, the page behind becomes inert and body scroll is locked. In push mode it opens non-modally inside a positioned container and slides the content you point it at (pushRef) aside by its own width. Drag the panel towards its edge to dismiss it: the release velocity is projected to decide whether it closes or springs back, and the backdrop and pushed content track the drag. The header and footer stay pinned while the body scrolls with contained overscroll. Focus moves in on open and returns to the trigger on close, Escape always closes, and with reduced motion the panel fades instead of sliding.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/side-drawer.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/side-drawer.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/side-drawer.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { SideDrawer } from "@/components/ui/side-drawer";
import { useState } from "react";

export function SideDrawerExample() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button type="button" onClick={() => setOpen(true)}>
        Open drawer
      </button>
      <SideDrawer
        open={open}
        onOpenChange={setOpen}
        title="Filter orders"
        footer={<button onClick={() => setOpen(false)}>Apply</button>}
      >
        <p>Drawer content goes here.</p>
      </SideDrawer>
    </>
  );
}

03Props

PropTypeDefaultDescription
openboolean—Open state (controlled).
defaultOpenbooleanfalseInitial open state (uncontrolled). Opening on first render does not move focus.
onOpenChange(open: boolean) => void—Called when the drawer asks to open or close.
titleReactNode—Required. Drawer title; labels the dialog.
descriptionReactNode—Supporting text; describes the dialog.
footerReactNode—Pinned footer under the scrolling body.
side"left" | "right""right"Edge the drawer slides in from.
mode"overlay" | "push""overlay"Modal overlay (showModal) or non-modal push inside the nearest positioned container.
pushRefRefObject<HTMLElement | null>—Push mode: the element shifted aside while open. Put it and the drawer in a relative container with overflow: clip (or hidden).
widthnumber380Panel width in px, capped at the viewport (or container) width minus 48px.
dismissiblebooleantrueAllow drag-to-close and backdrop clicks. Escape and the close button always close.
backdropOpacitynumber0.4Backdrop opacity when fully open (overlay mode).
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 380, damping: 38, mass: 1 }Spring for opening, closing and settling after a drag.
accentstring"#FF7448"Focus ring and drag grip colour.
closeLabelstring"Close"Accessible name of the close button.
classNamestring—Classes added to the panel.
styleCSSProperties—Inline styles merged onto the panel.
childrenReactNode—Drawer body; scrolls independently.

04Features

  • 01Overlay mode: native modal dialog in the top layer with backdrop, inert page and body scroll lock
  • 02Push mode: non-modal panel inside a container that slides your content aside by its width
  • 03Velocity drag-to-close; the backdrop and pushed content track the drag
  • 04Pinned header and footer around a body with contained overscroll
  • 05Controlled (open + onOpenChange) or uncontrolled (defaultOpen)
  • 06Reduced motion: the panel fades instead of sliding
motion · springmotion · tweeninput · draginput · clickinput · keyboard

05Accessibility

  • Overlay mode uses showModal(): the page behind is inert, focus moves into the drawer and returns to the trigger on close.
  • Push mode uses show() and moves focus to the first focusable element in the drawer; focus returns to the trigger on close.
  • The dialog is labelled by its title and described by its description.
  • Escape and a labelled close button always close the drawer, even when drag and backdrop dismissal are off.
  • The drag grip is decorative (aria-hidden); every drag action has a keyboard equivalent.
  • With prefers-reduced-motion the panel fades in and out and pushed content moves instantly.

06Limitations

  • With a mouse, the drawer drags from its header or edge grip so body text stays selectable; touch drags from anywhere.
  • Push mode translates the pushed element rather than resizing it, so its far edge is clipped by the container.