---
id: "packages/active-ui/drawer"
title: "Drawer"
description: "A panel that slides in from an edge and can be swiped away, for quick choices and detail on touch screens."
url: "/packages/active-ui/drawer"
locale: "en"
revision: "bd4671fc6edc5b03"
---

# Drawer

A drawer is a panel that slides in from the bottom (or another edge) and follows the finger: the
reader can drag it away to close it. It suits quick choices and short detail on a phone. On a wide
screen, where nobody swipes, a [sheet](/packages/active-ui/sheet) or a
[dialog](/packages/active-ui/dialog) is usually the better fit. It is built on Base UI's drawer.

**Example: From the bottom with a handle, and from the side**

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

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

import {useState} from 'react';

import {formatCurrency} from '@workspace/active-format';
import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from '@workspace/active-ui/components/primitives/drawer';

const AMOUNTS = [50, 100, 250];
// Amounts are written by active-format, in the language of the page.
const euro = (amount: number) =>
  formatCurrency({amount, currency: 'EUR'}, 'en', {decimals: 0});

/**
 * A panel that slides up from the bottom and can be swiped away, the shape
 * of a quick choice on a phone. The swipe handle shows that it can be
 * dragged; `swipeDirection="right"` makes a side panel of the same kind.
 */
export default function DrawerExample() {
  const [amount, setAmount] = useState<number | null>(null);
  return (
    <div className="flex flex-col items-center gap-4">
      <div className="flex flex-wrap justify-center gap-2">
        <Drawer showSwipeHandle>
          <DrawerTrigger render={<Button variant="outline" />}>
            Add a contribution
          </DrawerTrigger>
          <DrawerContent>
            <DrawerHeader>
              <DrawerTitle>Extra contribution</DrawerTitle>
              <DrawerDescription>
                Choose an amount; it is collected with the next payment.
              </DrawerDescription>
            </DrawerHeader>
            <div className="flex justify-center gap-2 p-4">
              {AMOUNTS.map((value) => (
                <DrawerClose
                  key={value}
                  render={<Button variant="outline" />}
                  onClick={() => setAmount(value)}
                >
                  {euro(value)}
                </DrawerClose>
              ))}
            </div>
            <DrawerFooter>
              <DrawerClose render={<Button variant="ghost" />}>
                Not now
              </DrawerClose>
            </DrawerFooter>
          </DrawerContent>
        </Drawer>

        <Drawer swipeDirection="right">
          <DrawerTrigger render={<Button variant="outline" />}>
            Open the side drawer
          </DrawerTrigger>
          <DrawerContent>
            <DrawerHeader>
              <DrawerTitle>Recent activity</DrawerTitle>
              <DrawerDescription>
                Swipe to the right, press Escape or click outside to close.
              </DrawerDescription>
            </DrawerHeader>
            <ul className="text-muted-foreground flex flex-col gap-2 p-4 text-sm">
              <li>Statement sent to 214 participants</li>
              <li>Contribution rate changed to 7.5%</li>
              <li>Scheme renamed</li>
            </ul>
          </DrawerContent>
        </Drawer>
      </div>
      <p className="text-muted-foreground text-sm">
        Extra contribution: {amount === null ? 'none' : euro(amount)}
      </p>
    </div>
  );
}
```

## Usage

```tsx
import {
  Drawer,
  DrawerContent,
  DrawerDescription,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from '@workspace/active-ui/components/primitives/drawer';

<Drawer showSwipeHandle>
  <DrawerTrigger render={<Button variant="outline" />}>Open</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Extra contribution</DrawerTitle>
      <DrawerDescription>Choose an amount.</DrawerDescription>
    </DrawerHeader>
  </DrawerContent>
</Drawer>;
```

## Anatomy

- `Drawer`: the state and the gesture. `swipeDirection` (default `down`) is the direction that
  closes it, and so the edge it comes from: `down` from the bottom, `up` from the top, `left` and
  `right` from the sides. `modal` (default `true`) adds the backdrop and makes the page inert.
  `snapPoints` lets it rest at several heights. `showSwipeHandle` draws the handle.
- `DrawerTrigger` and `DrawerClose`: open and close from any element.
- `DrawerContent`: the panel, with its backdrop when modal. It has no close button of its own:
  the gesture, `Escape`, a click outside and your `DrawerClose` close it.
- `DrawerHeader` with `DrawerTitle` and `DrawerDescription`, and `DrawerFooter`.
- `DrawerSwipeHandle`, `DrawerOverlay` and `DrawerPortal`: the parts `DrawerContent` already
  renders, for a layout of your own.

## What your application provides

- The title and description, and the actions inside.
- A way to close that does not need the gesture: a `DrawerClose` button, for readers who cannot
  swipe or do not know they can.

## Accessibility and keyboard

- Modal by default: the focus moves inside, stays there, and returns to the trigger on close.
- `Escape` closes it. The swipe handle is decorative (`aria-hidden`).
- The panel is named by its title and described by its description.

## API

### Drawer

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `showSwipeHandle` | `boolean` | no | Whether a handle is drawn at the edge, to show that the drawer can be swiped. |
| `actionsRef` | `RefObject<DrawerRootActions \| null>` | no | A ref to imperative actions. - `unmount`: Ends the closing phase of the drawer after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the drawer completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the drawer imperatively when called. |
| `children` | `PayloadChildRenderFunction<unknown> \| ReactNode` | no | The content of the drawer. |
| `defaultOpen` | `boolean` | no | Whether the drawer is initially open.  To render a controlled drawer, use the `open` prop instead. |
| `defaultSnapPoint` | `DrawerSnapPoint \| null` | no | The initial snap point value when uncontrolled. |
| `defaultTriggerId` | `string \| null` | no | ID of the trigger that the drawer is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open drawer. |
| `disablePointerDismissal` | `boolean` | no | Whether to prevent the drawer from closing on outside presses. For non-modal drawers, this also prevents the drawer from closing when focus moves outside of it. |
| `handle` | `DrawerHandle<unknown>` | no | A handle to associate the drawer with a trigger. If specified, allows detached triggers to control the drawer's open state. Can be created with the Drawer.createHandle() method. |
| `modal` | `"trap-focus" \| boolean` | no | Determines if the drawer enters a modal state when open. - `true`: user interaction is limited to just the drawer: 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 drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled. |
| `onOpenChange` | `((open: boolean, eventDetails: DrawerRoot.ChangeEventDetails) => void)` | no | Event handler called when the drawer is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` | no | Event handler called after any animations complete when the drawer is opened or closed. |
| `onSnapPointChange` | `((snapPoint: DrawerSnapPoint \| null, eventDetails: DrawerRoot.SnapPointChangeEventDetails) => void)` | no | Callback fired when the snap point changes. |
| `open` | `boolean` | no | Whether the drawer is currently open. |
| `snapPoint` | `DrawerSnapPoint \| null` | no | The currently active snap point. Use with `onSnapPointChange` to control the snap point. |
| `snapPoints` | `DrawerSnapPoint[]` | no | Snap points used to position the drawer. Use numbers between 0 and 1 to represent fractions of the viewport height, numbers greater than 1 as pixel values, or strings in `px`/`rem` units (for example, `'148px'` or `'30rem'`). |
| `snapToSequentialPoints` | `boolean` | no | Disables velocity-based snap skipping so drag distance determines the next snap point. |
| `swipeDirection` | `SwipeDirection` | no | The swipe direction used to dismiss the drawer. |
| `triggerId` | `string \| null` | no | ID of the trigger that the drawer is associated with. This is useful in conjunction with the `open` prop to create a controlled drawer. There's no need to specify this prop when the drawer is uncontrolled (that is, when the `open` prop is not set). |

### Other parts

**DrawerPortal**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| RefObject<HTMLElement \| ShadowRoot \| null> \| ShadowRoot \| null` | no | A parent element to render the portal element into. |
| `keepMounted` | `boolean` | no | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| `render` | `ComponentRenderFn<HTMLProps, DrawerPortalState> \| 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. |

**DrawerOverlay**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `forceRender` | `boolean` | no | Whether the backdrop is forced to render even when nested. |
| `render` | `ComponentRenderFn<HTMLProps, DrawerBackdropState> \| 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. |

**DrawerSwipeHandle**

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

DrawerSwipeHandle has no props of its own.

**DrawerTrigger**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `handle` | `DrawerHandle<unknown>` | no | A handle to associate the trigger with a drawer. Can be created with the Drawer.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 drawers in controlled mode (with the Drawer.Root `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 drawer when it is opened. |
| `render` | `ComponentRenderFn<HTMLProps, DrawerTriggerState> \| 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. |

**DrawerClose**

| 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, DrawerCloseState> \| 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. |

**DrawerContent**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` | no | Determines the element to focus when the drawer 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, 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 drawer is opened.  - `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, or `false`/`undefined` to do nothing. |
| `render` | `ComponentRenderFn<HTMLProps, DrawerPopupState> \| 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. |

**DrawerHeader**

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

DrawerHeader has no props of its own.

**DrawerFooter**

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

DrawerFooter has no props of its own.

**DrawerTitle**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DrawerTitleState> \| 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. |

**DrawerDescription**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DrawerDescriptionState> \| 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 Drawer documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
