# Shadcn Drawer

> Free shadcn/ui drawer component for React — the drag-to-dismiss panel built on vaul. Snap points, nested drawers, four directions, and the responsive Dialog-on-desktop / Drawer-on-mobile pattern. The examples official shadcn never shipped.

Source: https://designrevision.com/components/drawer

The **Shadcn Drawer** is the drag-to-dismiss panel — the canonical <a href="https://ui.shadcn.com/docs/components/drawer" rel="nofollow">shadcn/ui</a> drawer built on <a href="https://vaul.emilkowal.ski/" rel="nofollow">**vaul**</a>, with a drag handle, snap points, four directions, and nested stacking. Official ships it with a couple of demos; the examples below cover what you actually build on mobile — snap-point sheets, nested flows, command menus, and the responsive **Dialog-on-desktop / Drawer-on-mobile** pattern.

## Built on vaul

The Drawer is a thin, styled wrapper over <a href="https://vaul.emilkowal.ski/" rel="nofollow">**vaul**</a>, Emil Kowalski's React drawer library. Vaul owns the hard parts — the **drag-to-dismiss** gesture, momentum and velocity, **snap points**, background scaling, and **nested** drawer stacking — so the shadcn layer only adds Tailwind styling and your design tokens. Install it once:

```bash
npm install vaul
```

Everything below — directions, snap points, nesting — is a vaul feature surfaced through the ten-part shadcn API (`Drawer`, `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerFooter`, `DrawerTitle`, `DrawerDescription`, `DrawerClose`, `DrawerOverlay`, `DrawerPortal`).

## Drawer vs Sheet vs Dialog

All three are overlays; the choice is about **input model and placement**:

- **Drawer** (vaul) is **touch-native** — drag-to-dismiss, momentum, snap points. Best on mobile and for bottom sheets.
- **[Sheet](/components/sheet)** (Radix) slides from any edge **without** drag physics — the better desktop side panel.
- **[Dialog](/components/dialog)** is a centered modal for a single focused task.

The canonical real-world setup is a **Dialog or Sheet on desktop and a Drawer on mobile** for the same content — covered next.

## Responsive: Dialog on desktop, Drawer on mobile

This is the pattern shadcn documents for the Drawer, and it's the single most useful one. Detect the viewport with a small `useMediaQuery` hook and render a [Dialog](/components/dialog) above your breakpoint or a Drawer below it, reusing the **same form** in both:

```tsx
const isDesktop = useMediaQuery("(min-width: 768px)")
if (isDesktop) return <Dialog>…<ProfileForm /></Dialog>
return <Drawer>…<ProfileForm /></Drawer>
```

The Responsive example is the complete, copy-pasteable version with the hook included.

## Directions

Pass `direction` to `Drawer` — `"top"`, `"right"`, `"bottom"`, or `"left"` (default `"bottom"`). `DrawerContent` is **direction-aware**: bottom and top drawers get the rounded edge and the drag handle, while left and right drawers take a width (`sm:max-w-sm`) and fill the height. The Directions example shows all four from one row.

## Snap points

Snap points turn the drawer into a multi-stage sheet — a **peek** state that expands to full. Pass `snapPoints` (fractions like `0.4` or CSS values like `"300px"`) and control the active stop with `activeSnapPoint` / `setActiveSnapPoint`:

```tsx
<Drawer snapPoints={[0.4, 1]} activeSnapPoint={snap} setActiveSnapPoint={setSnap}>
```

The drawer opens at the first stop and the user drags between them; content past the visible height scrolls once expanded. No competitor ships this — see the Snap points example.

## Nested drawers

Vaul can **stack** drawers: opening a second one scales the first back to reveal the stack. Use vaul's `NestedRoot` for the inner drawer (our primitive stays byte-faithful to shadcn and doesn't re-export it, so import it directly):

```tsx
import { Drawer as DrawerPrimitive } from "vaul"
// …
<DrawerPrimitive.NestedRoot>{/* inner DrawerContent */}</DrawerPrimitive.NestedRoot>
```

The Nested drawers example shows a two-level flow.

## Scrollable content & forms

For long content, give `DrawerContent` a `max-h-[80vh]` (already the default for bottom drawers) and a `flex-1 overflow-y-auto` middle region so the header and footer stay put while the body scrolls. For input, control the `open` state and call `setOpen(false)` on submit — the Scrollable and With-a-form examples cover both. You can even drop a [Command](/components/command) palette inside a drawer for a mobile **⌘K**, as the Command menu example does.

## Background scaling & controlling

Vaul can scale the page **behind** the drawer for the iOS-style stacked look — set `shouldScaleBackground` on `Drawer` (and wrap your app in a `[vaul-drawer-wrapper]` element). Open declaratively with `DrawerTrigger`, or **controlled** from state via `open` / `onOpenChange` to open it from anywhere or close it after an action.

## Accessibility

Vaul builds on an accessible dialog foundation — it traps focus, restores it to the trigger on close, and closes on Escape and outside-click. Always include a `DrawerTitle` (and ideally a `DrawerDescription`); if your design hides the title, keep it in the DOM with `sr-only` so screen readers still announce the drawer, as the Command menu example does.

## Theming

The drawer reads your design tokens — `--background`, `--border`, `--muted` (the drag handle) — so it follows your theme, including dark mode, with no overrides. Style any region with a className to tune padding, borders, or width per use.

## Installation

### drawer

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer
```

**Dependencies:** vaul, utils

**Props**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| direction | "top" \| "right" \| "bottom" \| "left" | "bottom" | Which edge the drawer slides in from (on Drawer). |
| snapPoints | (number \| string)[] | — | Heights the drawer snaps to, e.g. [0.4, 1]. Pair with activeSnapPoint / setActiveSnapPoint. |
| shouldScaleBackground | boolean | false | Scales the page behind the drawer for the iOS-style stacked look (on Drawer). |
| open | boolean | — | Controlled open state (on Drawer). Pair with onOpenChange. |

**Usage & accessibility:** Ten parts (Drawer, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription, DrawerOverlay, DrawerPortal) built on vaul — the drag-to-dismiss drawer by emilkowalski. DrawerContent renders a drag handle for bottom drawers and is direction-aware (top/right/bottom/left). For nested drawers use vaul's NestedRoot directly. Animations require tw-animate-css.

```tsx
// components/ui/drawer.tsx
"use client"

import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"

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

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

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

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

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

function DrawerOverlay({
  className,
  ...props
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
  return (
    <DrawerPrimitive.Overlay
      data-slot="drawer-overlay"
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className
      )}
      {...props}
    />
  )
}

function DrawerContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
  return (
    <DrawerPortal data-slot="drawer-portal">
      <DrawerOverlay />
      <DrawerPrimitive.Content
        data-slot="drawer-content"
        className={cn(
          "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
          "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
          "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
          "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
          "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
          className
        )}
        {...props}
      >
        <div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
        {children}
      </DrawerPrimitive.Content>
    </DrawerPortal>
  )
}

function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="drawer-header"
      className={cn(
        "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
        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 DrawerTitle({
  className,
  ...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
  return (
    <DrawerPrimitive.Title
      data-slot="drawer-title"
      className={cn("text-foreground font-semibold", className)}
      {...props}
    />
  )
}

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

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

## Examples

### Basic

A bottom drawer with a drag handle, header, and footer — drag down or press Escape to dismiss.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-demo-01
```

**Dependencies:** @designrevision/drawer, button

```tsx
// components/ui/drawer-demo-01.tsx
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

export default function DrawerDemo01() {
  return (
    <Drawer>
      <DrawerTrigger className="inline-flex items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground">
        Open drawer
      </DrawerTrigger>
      <DrawerContent>
        <div className="mx-auto w-full max-w-sm">
          <DrawerHeader>
            <DrawerTitle>Are you absolutely sure?</DrawerTitle>
            <DrawerDescription>
              This action cannot be undone. Drag the handle down or press Escape
              to dismiss.
            </DrawerDescription>
          </DrawerHeader>
          <DrawerFooter>
            <button className="inline-flex items-center justify-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow hover:bg-primary/90">
              Submit
            </button>
            <DrawerClose className="inline-flex items-center justify-center gap-2 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground">
              Cancel
            </DrawerClose>
          </DrawerFooter>
        </div>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### Responsive (Dialog + Drawer)

A **Dialog on desktop** and a **Drawer on mobile** via `useMediaQuery`, sharing one form — the canonical shadcn responsive pattern.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-responsive-01
```

**Dependencies:** @designrevision/drawer, dialog, button, input, label

```tsx
// components/ui/drawer-responsive-01.tsx
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

function useMediaQuery(query: string) {
  const [matches, setMatches] = React.useState(false)

  React.useEffect(() => {
    const result = window.matchMedia(query)
    const onChange = () => setMatches(result.matches)
    onChange()
    result.addEventListener("change", onChange)
    return () => result.removeEventListener("change", onChange)
  }, [query])

  return matches
}

function ProfileForm({ className }: React.ComponentProps<"form">) {
  return (
    <form className={cn("grid items-start gap-4", className)}>
      <div className="grid gap-2">
        <Label htmlFor="email">Email</Label>
        <Input type="email" id="email" defaultValue="ada@example.com" />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="username">Username</Label>
        <Input id="username" defaultValue="@ada" />
      </div>
      <Button type="submit">Save changes</Button>
    </form>
  )
}

export default function DrawerResponsive01() {
  const [open, setOpen] = React.useState(false)
  const isDesktop = useMediaQuery("(min-width: 768px)")

  if (isDesktop) {
    return (
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogTrigger asChild>
          <Button variant="outline">Edit profile</Button>
        </DialogTrigger>
        <DialogContent className="sm:max-w-[425px]">
          <DialogHeader>
            <DialogTitle>Edit profile</DialogTitle>
            <DialogDescription>
              Make changes to your profile here. Click save when you&apos;re
              done.
            </DialogDescription>
          </DialogHeader>
          <ProfileForm />
        </DialogContent>
      </Dialog>
    )
  }

  return (
    <Drawer open={open} onOpenChange={setOpen}>
      <DrawerTrigger asChild>
        <Button variant="outline">Edit profile</Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader className="text-left">
          <DrawerTitle>Edit profile</DrawerTitle>
          <DrawerDescription>
            Make changes to your profile here. Click save when you&apos;re done.
          </DrawerDescription>
        </DrawerHeader>
        <ProfileForm className="px-4" />
        <DrawerFooter className="pt-2">
          <DrawerClose asChild>
            <Button variant="outline">Cancel</Button>
          </DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### Directions

Open from any edge — `top`, `right`, `bottom`, or `left` — with the `direction` prop on `Drawer`.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-directions-01
```

**Dependencies:** @designrevision/drawer, button

```tsx
// components/ui/drawer-directions-01.tsx
import { Button } from "@/components/ui/button"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

const directions = ["top", "right", "bottom", "left"] as const

export default function DrawerDirections01() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {directions.map((direction) => (
        <Drawer key={direction} direction={direction}>
          <DrawerTrigger asChild>
            <Button variant="outline" className="capitalize">
              {direction}
            </Button>
          </DrawerTrigger>
          <DrawerContent>
            <DrawerHeader>
              <DrawerTitle className="capitalize">{direction} drawer</DrawerTitle>
              <DrawerDescription>
                This drawer slides in from the {direction}. Set it with the{" "}
                <code>direction</code> prop on <code>Drawer</code>.
              </DrawerDescription>
            </DrawerHeader>
            <DrawerFooter>
              <DrawerClose asChild>
                <Button variant="outline">Close</Button>
              </DrawerClose>
            </DrawerFooter>
          </DrawerContent>
        </Drawer>
      ))}
    </div>
  )
}
```

---

### Snap points

A drawer that opens at 40% height and snaps to full when dragged — vaul `snapPoints={[0.4, 1]}`.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-snap-01
```

**Dependencies:** @designrevision/drawer, button

```tsx
// components/ui/drawer-snap-01.tsx
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

const snapPoints = [0.4, 1]

export default function DrawerSnap01() {
  const [snap, setSnap] = React.useState<number | string | null>(snapPoints[0])

  return (
    <Drawer
      snapPoints={snapPoints}
      activeSnapPoint={snap}
      setActiveSnapPoint={setSnap}
    >
      <DrawerTrigger asChild>
        <Button variant="outline">Open snap drawer</Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>Drag to expand</DrawerTitle>
          <DrawerDescription>
            This drawer opens at 40% height. Drag the handle up to snap it to
            full height.
          </DrawerDescription>
        </DrawerHeader>
        <div className="px-4">
          <p className="text-sm text-muted-foreground">
            Active snap point:{" "}
            <span className="font-medium text-foreground">
              {snap === 1 ? "full" : "40%"}
            </span>
          </p>
          <div className="mt-4 grid gap-3">
            {Array.from({ length: 6 }, (_, i) => i + 1).map((n) => (
              <div
                key={n}
                className="rounded-md border border-border p-3 text-sm"
              >
                Row {n} — scroll appears once the drawer is at full height.
              </div>
            ))}
          </div>
        </div>
        <DrawerFooter>
          <DrawerClose asChild>
            <Button variant="outline">Close</Button>
          </DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### Nested drawers

Stack a second drawer on the first with vaul `NestedRoot` — the one behind scales back to show the stack.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-nested-01
```

**Dependencies:** vaul, @designrevision/drawer, button

```tsx
// components/ui/drawer-nested-01.tsx
"use client"

// Nested drawers use vaul's NestedRoot, which our primitive intentionally does
// not re-export (to stay byte-faithful to shadcn/ui). Importing it directly is
// the documented vaul pattern for stacking one drawer on top of another.
import { Drawer as DrawerPrimitive } from "vaul"

import { Button } from "@/components/ui/button"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

export default function DrawerNested01() {
  return (
    <Drawer>
      <DrawerTrigger asChild>
        <Button variant="outline">Open drawer</Button>
      </DrawerTrigger>
      <DrawerContent>
        <div className="mx-auto w-full max-w-sm">
          <DrawerHeader>
            <DrawerTitle>First drawer</DrawerTitle>
            <DrawerDescription>
              Open a nested drawer to stack a second panel on top — the one
              behind scales back to reveal the stack.
            </DrawerDescription>
          </DrawerHeader>
          <DrawerFooter>
            <DrawerPrimitive.NestedRoot>
              <DrawerTrigger asChild>
                <Button>Open nested drawer</Button>
              </DrawerTrigger>
              <DrawerContent>
                <div className="mx-auto w-full max-w-sm">
                  <DrawerHeader>
                    <DrawerTitle>Nested drawer</DrawerTitle>
                    <DrawerDescription>
                      Closing this returns you to the first drawer.
                    </DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <DrawerClose asChild>
                      <Button variant="outline">Close</Button>
                    </DrawerClose>
                  </DrawerFooter>
                </div>
              </DrawerContent>
            </DrawerPrimitive.NestedRoot>
            <DrawerClose asChild>
              <Button variant="outline">Close</Button>
            </DrawerClose>
          </DrawerFooter>
        </div>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### Scrollable

A sticky header and footer with a scrollable body — `flex-1 overflow-y-auto` for long content.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-scroll-01
```

**Dependencies:** @designrevision/drawer, button

```tsx
// components/ui/drawer-scroll-01.tsx
import { Button } from "@/components/ui/button"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

const events = Array.from({ length: 20 }, (_, i) => i + 1)

export default function DrawerScroll01() {
  return (
    <Drawer>
      <DrawerTrigger asChild>
        <Button variant="outline">View activity</Button>
      </DrawerTrigger>
      <DrawerContent className="max-h-[80vh]">
        <DrawerHeader className="border-b">
          <DrawerTitle>Activity</DrawerTitle>
        </DrawerHeader>
        <div className="flex-1 overflow-y-auto p-4">
          <div className="mx-auto grid w-full max-w-md gap-3">
            {events.map((n) => (
              <div
                key={n}
                className="rounded-md border border-border p-3 text-sm"
              >
                <p className="font-medium">Event #{n}</p>
                <p className="text-muted-foreground">
                  Something happened a little while ago.
                </p>
              </div>
            ))}
          </div>
        </div>
        <DrawerFooter className="border-t">
          <DrawerClose asChild>
            <Button>Close</Button>
          </DrawerClose>
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### With a form

A **controlled** drawer holding a payment form that closes itself on submit via `setOpen(false)`.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-form-01
```

**Dependencies:** @designrevision/drawer, button, input, label

```tsx
// components/ui/drawer-form-01.tsx
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

export default function DrawerForm01() {
  const [open, setOpen] = React.useState(false)

  return (
    <Drawer open={open} onOpenChange={setOpen}>
      <DrawerTrigger asChild>
        <Button variant="outline">Add payment method</Button>
      </DrawerTrigger>
      <DrawerContent>
        <div className="mx-auto w-full max-w-sm">
          <DrawerHeader>
            <DrawerTitle>Add payment method</DrawerTitle>
            <DrawerDescription>
              Enter your card details. They are saved when you submit.
            </DrawerDescription>
          </DrawerHeader>
          <form
            onSubmit={(event) => {
              event.preventDefault()
              setOpen(false)
            }}
            className="grid gap-4 px-4"
          >
            <div className="grid gap-2">
              <Label htmlFor="card-name">Name on card</Label>
              <Input id="card-name" defaultValue="Ada Lovelace" />
            </div>
            <div className="grid gap-2">
              <Label htmlFor="card-number">Card number</Label>
              <Input id="card-number" defaultValue="4242 4242 4242 4242" />
            </div>
            <DrawerFooter className="px-0">
              <Button type="submit">Save card</Button>
              <DrawerClose asChild>
                <Button type="button" variant="outline">
                  Cancel
                </Button>
              </DrawerClose>
            </DrawerFooter>
          </form>
        </div>
      </DrawerContent>
    </Drawer>
  )
}
```

---

### Command menu

A mobile **⌘K** — a [Command](/components/command) palette inside a drawer that closes when an item is picked.

**Install:**

```bash
npx shadcn@latest add @designrevision/drawer-command-01
```

**Dependencies:** lucide-react, @designrevision/drawer, command

```tsx
// components/ui/drawer-command-01.tsx
"use client"

import * as React from "react"
import {
  CalendarIcon,
  SearchIcon,
  SettingsIcon,
  SmileIcon,
  UserIcon,
} from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command"
import {
  Drawer,
  DrawerContent,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"

export default function DrawerCommand01() {
  const [open, setOpen] = React.useState(false)
  const close = () => setOpen(false)

  return (
    <Drawer open={open} onOpenChange={setOpen}>
      <DrawerTrigger asChild>
        <Button variant="outline">
          <SearchIcon />
          Search…
        </Button>
      </DrawerTrigger>
      <DrawerContent>
        <DrawerHeader className="sr-only">
          <DrawerTitle>Command menu</DrawerTitle>
        </DrawerHeader>
        <Command className="rounded-none border-t">
          <CommandInput placeholder="Type a command or search…" />
          <CommandList>
            <CommandEmpty>No results found.</CommandEmpty>
            <CommandGroup heading="Suggestions">
              <CommandItem onSelect={close}>
                <CalendarIcon />
                Calendar
              </CommandItem>
              <CommandItem onSelect={close}>
                <SmileIcon />
                Search emoji
              </CommandItem>
            </CommandGroup>
            <CommandGroup heading="Settings">
              <CommandItem onSelect={close}>
                <UserIcon />
                Profile
              </CommandItem>
              <CommandItem onSelect={close}>
                <SettingsIcon />
                Settings
              </CommandItem>
            </CommandGroup>
          </CommandList>
        </Command>
      </DrawerContent>
    </Drawer>
  )
}
```
