A bottom sheet you can drag down to dismiss.

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

export default function DrawerDemo() {
  return (
    <Drawer>
      <DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>New project</DrawerTitle>
          <DrawerDescription>Give it a name. Drag the handle down, or press Escape, to dismiss.</DrawerDescription>
        </DrawerHeader>
        <div className="mt-4 grid gap-1.5">
          <Label htmlFor="drawer-name">Project name</Label>
          <Input id="drawer-name" placeholder="Reading room" />
        </div>
        <DrawerFooter>
          <DrawerClose render={<Button>Create project</Button>} />
          <DrawerClose render={<Button variant="outline">Cancel</Button>} />
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  )
}

Built on Base UI

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/drawer.json

Needs a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.

Drawer imports cn() from lib/utils.ts — add it too if you don't have it yet:

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage

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

<Drawer>
  <DrawerTrigger render={<Button variant="outline">Open drawer</Button>} />
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>New project</DrawerTitle>
      <DrawerDescription>Give it a name.</DrawerDescription>
    </DrawerHeader>
    <DrawerFooter>
      <DrawerClose render={<Button variant="outline">Cancel</Button>} />
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Examples

Scrollable content

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

const notes = Array.from({ length: 14 }, (_, i) => ({
  version: `1.${14 - i}.0`,
  text: 'Smaller bundles, steadier focus handling and a handful of visual fixes.',
}))

export default function DrawerScrollable() {
  return (
    <Drawer>
      <DrawerTrigger render={<Button variant="outline">Release notes</Button>} />
      <DrawerContent>
        <DrawerHeader>
          <DrawerTitle>Release notes</DrawerTitle>
          <DrawerDescription>The drawer stops at 85% of the screen and scrolls when its content is longer.</DrawerDescription>
        </DrawerHeader>
        <ul className="mt-4 grid gap-4">
          {notes.map(note => (
            <li key={note.version} className="grid gap-1 border-b border-border pb-4 last:border-b-0">
              <span className="text-sm font-medium">v{note.version}</span>
              <span className="text-sm text-muted-foreground">{note.text}</span>
            </li>
          ))}
        </ul>
        <DrawerFooter>
          <DrawerClose render={<Button variant="outline">Close</Button>} />
        </DrawerFooter>
      </DrawerContent>
    </Drawer>
  )
}

API Reference

NameTypeDefaultDescription
open / onOpenChangeboolean / (open: boolean) => void—Control the open state from outside (set on Drawer).
defaultOpenbooleanfalseInitial open state (uncontrolled).
modalboolean | 'trap-focus'trueTraps focus and locks page scroll while open.
disablePointerDismissalbooleanfalseStops outside clicks from closing the drawer.
DrawerTrigger renderReactElement—Renders the trigger as a different element, e.g. a Button.
DrawerClose renderReactElement—Renders a close control as a different element.
Dismissing——Drag the sheet down (mouse, touch or pen), press Escape, click the backdrop, or use a DrawerClose. Focus returns to the trigger.
DrawerContent——A bottom sheet with a handle bar, capped at 85% of the screen height; its content scrolls when longer. For other edges use Sheet.