# Sheet

> A panel that slides in from an edge and can be thrown back: drag it toward its edge and release speed decides whether it closes.

Side panels usually close only with a button, and bottom sheets that drag use a fixed distance that feels wrong on a short flick. Here you grab the header (or the grip) and pull: it follows 1:1, resists the wrong way, and a throw projected from your release speed or a pull past a third closes it; otherwise it springs back.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/sheet.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: none
- Hairline UI components it needs (the CLI installs them too): `ui-interaction-motion`
- Docs and live preview: https://hairlineui.com/ui/sheet
- Registry item (source): https://hairlineui.com/r/sheet.json

## Usage

```tsx
import { Sheet } from "@/components/ui/sheet";

<Sheet trigger={<button>Filters</button>} side="bottom" title="Filters">
  …
</Sheet>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"right" \| "left" \| "bottom"` | `"right"` | The edge it comes from. |
| `open / defaultOpen / onOpenChange` | `boolean / (open) => void` | – | State. |
| `trigger` | `ReactElement` | – | A button that opens it. |
| `title / description` | `ReactNode` | – | Header. |
| `children / footer` | `ReactNode \| ({ close }) => ReactNode` | – | Body and buttons. |
| `width` | `number` | `400` | Side sheet width. |

## States

- **open**: Slides in from its edge (460ms) as the page fades back.
- **drag**: Follows the finger 1:1 after 6px of slop; the wrong way resists.
- **release**: Projected past a third: it carries on out (300ms); otherwise it springs back (380ms).

## What it handles

Drag to dismiss needs slop, rubber-banding and velocity projection to feel right; distance alone makes short flicks fail.

- **Thrown, not just dragged**: projectInertia decides with release velocity, so a short flick closes it.
- **Rubber band**: Pulling the wrong way resists instead of stopping dead.
- **Three edges**: right, left and bottom; the bottom one has a grip.
- **Native dialog**: Focus trap, Escape and an inert page.

## Replaces

Vaul, Radix Dialog as a drawer, gesture code

## Where it fits

- **Detail panels**: An order or ticket beside the list it came from.
- **Mobile menus and filters**: A bottom sheet that drags away.
- **Settings drawers**: Preferences without leaving the page.
- **Carts**: A cart that slides in from the right.

## Notes

- Built on the native <dialog> (modal.ts, shared with the other modal surface): focus stays inside, the page is inert, Escape works, and it sits in the top layer.
- Drag writes transforms to the panel directly: no re-render per move.
- Reduced motion: it fades; drag still works.

License: MIT (https://hairlineui.com/license#components)
