<!-- Protocore Design System — Sheet -->
# Sheet

- **Category:** Overlay (`overlay`)
- **Slug:** `overlay/sheet`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Sheet } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/overlay/sheet

> A full-height panel that slides in from the right for detail views and side forms that shouldn't cover the whole screen.

## When to use it

A Sheet is the **detail drawer**: inspect a row, review a record, edit in a side form — while the list or page stays visible behind it for context. It's modal (backdrop + focus trap) but occupies an edge rather than centering, which suits taller content and keeps the user oriented.

Choose a **[Dialog](/overlay/dialog)** instead when the task is short and self-contained and a centered card reads better. Choose a **[Popover](/overlay/popover)** when the content is small, anchored to a control, and needn't block the page. Reach for a Sheet specifically when the content is a **long, scrollable detail or form** tied to something the user selected — the classic master-detail pattern within an **[AppShell](/layout/app-shell)**.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `showClose` | `boolean` | no | `true` | Render a top-right 32px ghost × close button. |
| `size` | `enum` | no | `md` | Panel width. sm 360 / md 480 / lg 640. |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

Built on the Dialog primitive, so the parts match: `Sheet.Root` / `Trigger` / `Content` / `Title` / `Description` / `Footer` / `Close`. The panel slides in from the right over a dimmed backdrop and keeps the page context visible beside it.

```tsx
import { Sheet, Button, Text, VStack, DefinitionList } from "@protocore/pds";

export default function SheetBasics() {
  return (
    <Sheet.Root>
      <Sheet.Trigger asChild>
        <Button variant="secondary">Inspect request</Button>
      </Sheet.Trigger>
      <Sheet.Content>
        <Sheet.Title>Request 8f21a4</Sheet.Title>
        <Sheet.Description>POST /v1/ledger/append · 42ms</Sheet.Description>
        <VStack gap={4} style={{ marginTop: 20 }}>
          <DefinitionList
            items={[
              { term: "Status", description: "200 OK" },
              { term: "Region", description: "eu-central-1" },
              { term: "Actor", description: "ci-deploy-bot" },
              { term: "Bytes", description: "1,204" },
            ]}
          />
          <Text size="sm" color="secondary">
            Full trace retained for 30 days.
          </Text>
        </VStack>
        <Sheet.Footer>
          <Sheet.Close asChild>
            <Button variant="secondary">Close</Button>
          </Sheet.Close>
        </Sheet.Footer>
      </Sheet.Content>
    </Sheet.Root>
  );
}
```

### Sizes

`size` sets the panel width: `sm` 360 (quick detail), `md` 480 (the default drawer), `lg` 640 (forms and side-by-side content).

```tsx
import { Sheet, Button, HStack, Text } from "@protocore/pds";

const sizes = ["sm", "md", "lg"] as const;

export default function SheetSizes() {
  return (
    <HStack gap={3}>
      {sizes.map((size) => (
        <Sheet.Root key={size}>
          <Sheet.Trigger asChild>
            <Button variant="secondary">{size}</Button>
          </Sheet.Trigger>
          <Sheet.Content size={size}>
            <Sheet.Title>Panel {size}</Sheet.Title>
            <Sheet.Description>
              {size === "sm"
                ? "360px — quick detail or confirmation."
                : size === "md"
                  ? "480px — the default detail drawer."
                  : "640px — forms and side-by-side content."}
            </Sheet.Description>
            <Text size="sm" color="secondary" style={{ marginTop: 16 }}>
              size=&quot;{size}&quot;
            </Text>
          </Sheet.Content>
        </Sheet.Root>
      ))}
    </HStack>
  );
}
```

## Do & don't

**Do**

- Use it for master-detail: keep the list visible, show the record in the drawer.
- Pick a size that fits the content — sm for detail, lg for forms.
- Include a Sheet.Title for accessibility, as with Dialog.
- Pin actions in Sheet.Footer so they stay reachable as content scrolls.

**Don't**

- Use a Sheet for a two-line confirmation — that's a Dialog or AlertDialog.
- Stack multiple Sheets; navigate within one panel instead.
- Make it so wide it becomes a full-page takeover — that should be a route.
- Omit the Title — Radix warns and screen readers need the label.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Esc` | Closes the sheet and returns focus to the trigger. |
| `Tab` | Cycles focus forward, trapped within the panel. |
| `Shift + Tab` | Cycles focus backward, trapped within the panel. |
| `Enter / Space` | Activates the focused control. |

**Notes**

- Built on Radix Dialog: role=dialog with aria-modal, focus trapped and restored on close.
- A Sheet.Title is required to label the panel; Description describes it.
- The close × is keyboard-reachable and labelled; the backdrop dims and inerts the page.
- Content outside the sheet is hidden from assistive tech while it's open.

## Related

`dialog`, `popover`, `app-shell`, `definition-list`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
