# Sheet and Drawer

> A panel that slides in from an edge over a scrim, and a bottom Drawer with a drag handle.

Category: Overlays. Docs page: https://rime.mjsons.net/components/sheet

## Installation

```bash
npx shadcn@latest add KezLahd/rime/sheet
```

Sheet is a dialog flush to one edge (side: right, left, top or bottom) for secondary tasks that keep the page in view: a record's details, filters, settings. It traps focus, locks page scroll, closes on Esc, the scrim and the close button, and returns focus to whatever opened it.

Drawer is the bottom sheet, centred to a readable width on wide screens, with a grab handle. With dragToClose (on by default) a downward drag on the handle past about a third of its height dismisses it.

The header keeps one left edge: title and description stacked flush left, the close button at the right. The body scrolls on its own with the themed scrollbar; footer buttons sit along the bottom edge, primary last. Size is size (any CSS length) or --sheet-size.

inline renders the open panel in the page flow: no portal, scrim, focus trap, scroll lock or outside-click listener. It exists for docs, previews and static mock-ups; never ship it as the live overlay.

## Usage

```tsx
import { Button, Drawer, Sheet } from "@/components/ui";

<Sheet
  open={open}
  onClose={() => setOpen(false)}
  title="Edit team member"
  footer={<Button onClick={save}>Save changes</Button>}
>
  …fields…
</Sheet>
```

## Props: Sheet

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` (required) | `boolean` |  |  |
| `onClose` (required) | `() => void` |  | Esc, the scrim and the close button. |
| `title` (required) | `ReactNode` |  |  |
| `description` | `ReactNode` |  |  |
| `children` | `ReactNode` |  |  |
| `footer` | `ReactNode` |  | Buttons along the bottom edge, primary last. |
| `side` | `SheetSide` | `"right"` | The edge it slides in from. |
| `size` | `string` |  | Width (left, right) or height (top, bottom), any CSS length. Defaults to --sheet-size, 420px. |
| `closeLabel` | `string` | `"Close"` |  |
| `inline` | `boolean` |  | Render the open panel in the page flow: no portal, scrim, focus trap or scroll lock (docs, previews). |
| `className` | `string` |  |  |

## Props: Drawer

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `footer` | `ReactNode` |  | Buttons along the bottom edge, primary last. |
| `title` (required) | `ReactNode` |  |  |
| `className` | `string` |  |  |
| `children` | `ReactNode` |  |  |
| `open` (required) | `boolean` |  |  |
| `onClose` (required) | `() => void` |  | Esc, the scrim and the close button. |
| `description` | `ReactNode` |  |  |
| `closeLabel` | `string` |  |  |
| `inline` | `boolean` |  | Render the open panel in the page flow: no portal, scrim, focus trap or scroll lock (docs, previews). |
| `dragToClose` | `boolean` | `true` | Let a downward drag on the handle dismiss it. On by default. |

## Examples

### Open

The panel itself, drawn inline.

```tsx
<Sheet inline open onClose={close} title="Edit team member" description="Changes apply as soon as you save." footer={…}>
  …fields…
</Sheet>
```

### From each edge

```tsx
<Sheet open={open} side="left" onClose={close} title="Filters">…</Sheet>
```

### Drawer, open

```tsx
<Drawer inline open onClose={close} title="Order ORD-2041" footer={<Button>Mark as shipped</Button>}>…</Drawer>
```

### Drawer with drag to close

Drag the handle down to dismiss.

```tsx
<Drawer open={open} onClose={() => setOpen(false)} title="Order ORD-2041">…</Drawer>
```

## Theme tokens

Per-component tokens. Undeclared by default: the default below is the fallback the CSS uses. Set any of them at :root or on a wrapper to restyle every instance.

| Token | Default |
| --- | --- |
| `--drawer-width` | `640px` |
| `--sheet-radius` | `var(--r-xl)` |
| `--sheet-size` | `420px` / `85dvh` / `380px` |
| `--sheet-title-size` | `18px` |

System tokens it reads: `--border-w`, `--density`, `--dur`, `--ease`, `--ease-out`, `--font-display`, `--ink-body`, `--ink-heading`, `--ink-secondary`, `--line-glass`, `--modal-blur`, `--modal-scrim`, `--modal-scrim-blur`, `--modal-shadow`, `--modal-surface`, `--r-full`, `--r-xl`, `--rgb-brand-deep`, `--text-small`, `--tracking-display`, `--z-modal`.

## Accessibility

- role="dialog" with aria-modal, labelled by the title and described by the description.
- Focus moves in on open, Tab is trapped, Esc closes, and focus returns to the opener. The drag handle is pointer-only; Esc and the close button are always there.
- Reduced motion: it fades in place instead of sliding.

## Do

- Use a right sheet for detail and edit panels, a bottom drawer on phones.
- Keep one primary action in the footer.

## Don't

- Don't stack a sheet on a modal.
- Don't hide navigation in a sheet on desktop; that is the sidebar.

Related: https://rime.mjsons.net/components/modal, https://rime.mjsons.net/components/alert-dialog
