---
id: "packages/active-ui/sheet"
title: "Sheet"
description: "A panel that slides in from an edge of the screen for work beside the page, such as filters or details."
url: "/packages/active-ui/sheet"
locale: "en"
revision: "8df6fcb3937cfe32"
---

# Sheet

A sheet is a [dialog](/packages/active-ui/dialog) shaped as a panel along an edge of the screen. Use
it for a task that belongs to the page but needs room: filters, the details of a row, a short
form. The page stays visible behind it. On a phone, consider a [drawer](/packages/active-ui/drawer),
which can be swiped away.

**Example: Filters from each side**

Client: Runs entirely in your browser. Source: `examples/ui/sheet.tsx`.

```tsx
// apps/docs/examples/ui/sheet.tsx
'use client';

import {useState} from 'react';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {Checkbox} from '@workspace/active-ui/components/primitives/checkbox';
import {
  Field,
  FieldGroup,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@workspace/active-ui/components/primitives/sheet';

const SIDES = ['right', 'left', 'top', 'bottom'] as const;
const STATUSES = ['Active', 'Paused', 'Closed'];

/**
 * A panel that slides in from an edge, for work beside the page: here a set
 * of filters that applies only when the reader confirms. The panel keeps its
 * own draft; `SheetClose` closes it from the footer buttons.
 */
export default function SheetExample() {
  const [side, setSide] = useState<(typeof SIDES)[number]>('right');
  const [applied, setApplied] = useState<string[]>(['Active']);
  const [draft, setDraft] = useState<string[]>(applied);
  return (
    <div className="flex flex-col items-center gap-4">
      <div className="flex flex-wrap justify-center gap-2">
        {SIDES.map((option) => (
          <Sheet
            key={option}
            onOpenChange={(open) => {
              if (open) {
                setSide(option);
                setDraft(applied);
              }
            }}
          >
            <SheetTrigger render={<Button variant="outline" />}>
              Filters ({option})
            </SheetTrigger>
            <SheetContent side={side} closeLabel="Close filters">
              <SheetHeader>
                <SheetTitle>Filter schemes</SheetTitle>
                <SheetDescription>
                  Show only the schemes with these statuses.
                </SheetDescription>
              </SheetHeader>
              <FieldGroup className="gap-3 px-4">
                {STATUSES.map((status) => (
                  <Field key={status} orientation="horizontal">
                    <Checkbox
                      id={`sheet-${option}-${status}`}
                      checked={draft.includes(status)}
                      onCheckedChange={(checked) =>
                        setDraft((current) =>
                          checked
                            ? [...current, status]
                            : current.filter((value) => value !== status),
                        )
                      }
                    />
                    <FieldLabel htmlFor={`sheet-${option}-${status}`}>
                      {status}
                    </FieldLabel>
                  </Field>
                ))}
              </FieldGroup>
              <SheetFooter>
                <SheetClose
                  render={<Button />}
                  onClick={() => setApplied(draft)}
                >
                  Apply filters
                </SheetClose>
                <SheetClose render={<Button variant="outline" />}>
                  Cancel
                </SheetClose>
              </SheetFooter>
            </SheetContent>
          </Sheet>
        ))}
      </div>
      <p className="text-muted-foreground text-sm">
        Showing: {applied.length ? applied.join(', ') : 'nothing'}
      </p>
    </div>
  );
}
```

## Usage

```tsx
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@workspace/active-ui/components/primitives/sheet';

<Sheet>
  <SheetTrigger render={<Button variant="outline" />}>Filters</SheetTrigger>
  <SheetContent side="right" closeLabel="Close filters">
    <SheetHeader>
      <SheetTitle>Filter schemes</SheetTitle>
      <SheetDescription>
        Show only the schemes with these statuses.
      </SheetDescription>
    </SheetHeader>
  </SheetContent>
</Sheet>;
```

## Anatomy

- `Sheet`: the open state; `open` and `onOpenChange`, or uncontrolled.
- `SheetTrigger` and `SheetClose`: open and close from any element (`render` for your own).
- `SheetContent`: the panel with its backdrop. `side`: `right` (the default), `left`, `top` or
  `bottom`. A side panel is three quarters of the screen wide up to `24rem`; a top or bottom one
  as high as its content.
- `SheetHeader` with `SheetTitle` and `SheetDescription`; `SheetFooter` at the bottom of the
  panel, for its actions.

## What your application provides

- `closeLabel`: the accessible name of the close button in the corner. It is required while the
  button is shown and not allowed with `showCloseButton={false}`.
- The title, description and content, and what the actions do. A sheet that edits something keeps
  a draft and applies it on confirm, as the example does; a sheet that only shows something needs
  no footer.

## Accessibility and keyboard

- A modal dialog: the focus moves inside on open and stays there; the page behind is inert.
- `Escape` and a click on the backdrop close it; the focus returns to the trigger.
- The panel is named by its title and described by its description.

## API

### SheetContent

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `closeLabel` | `string` | no | The accessible name of the close button; required while it is shown. |
| `showCloseButton` | `boolean` | no | Whether the close button is shown in the corner. |
| `side` | `"bottom" \| "left" \| "right" \| "top"` | no | The edge the sheet slides in from. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` | no | Determines the element to focus when the dialog is closed.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| HTMLElement \| null \| void)` | no | Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| `render` | `ComponentRenderFn<HTMLProps, DialogPopupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### Other parts

**Sheet**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `actionsRef` | `RefObject<DialogRootActions \| null>` | no | A ref to imperative actions. - `unmount`: Ends the closing phase of the dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the dialog imperatively when called. |
| `children` | `PayloadChildRenderFunction<unknown> \| ReactNode` | no | The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger. |
| `defaultOpen` | `boolean` | no | Whether the dialog is initially open.  To render a controlled dialog, use the `open` prop instead. |
| `defaultTriggerId` | `string \| null` | no | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. |
| `disablePointerDismissal` | `boolean` | no | Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. |
| `handle` | `DialogHandle<unknown>` | no | A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. |
| `modal` | `"trap-focus" \| boolean` | no | Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled.  When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so touch screen readers can escape the popup. |
| `onOpenChange` | `((open: boolean, eventDetails: DialogRoot.ChangeEventDetails) => void)` | no | Event handler called when the dialog is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` | no | Event handler called after any animations complete when the dialog is opened or closed. |
| `open` | `boolean` | no | Whether the dialog is currently open. |
| `triggerId` | `string \| null` | no | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). |

**SheetTrigger**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `handle` | `DialogHandle<unknown>` | no | A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. |
| `id` | `string` | no | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `payload` | `unknown` | no | A payload to pass to the dialog when it is opened. |
| `render` | `ComponentRenderFn<HTMLProps, DialogTriggerState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**SheetClose**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ComponentRenderFn<HTMLProps, DialogCloseState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**SheetHeader**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

SheetHeader has no props of its own.

**SheetFooter**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

SheetFooter has no props of its own.

**SheetTitle**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DialogTitleState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**SheetDescription**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DialogDescriptionState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Sheet documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
