# Drawer

An edge-anchored panel that slides in for details, navigation, and side tasks.

## Installation

```tsx
import { Drawer, DrawerTrigger, DrawerPanel } from "@makeplane/propel/components/drawer";
```
## Usage

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerHeaderContent,
  DrawerHeaderIcon,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { CubeOutline, CloseOutline } from "@makeplane/propel/icons";

/**
 * Default use case: `sm` inline flush column absolutely positioned in a `relative` parent —
 * overlays page content without pushing it.
 */
export default function BasicDemo() {
  return (
    <div className="relative h-[28rem] w-full overflow-hidden rounded-xl border border-subtle bg-layer-2">
      <Drawer swipeDirection="right">
        <div className="flex h-full items-center justify-center p-4">
          <DrawerTrigger
            render={<Button stretch="auto" variant="secondary" size="lg" label="Open details" />}
          />
        </div>
        <DrawerPanel variant="inline" side="end" size="sm">
          <DrawerHeader>
            <DrawerHeaderStart>
              <DrawerHeaderIcon>
                <CubeOutline />
              </DrawerHeaderIcon>
              <DrawerHeaderContent>
                <DrawerTitle>Page details</DrawerTitle>
                <DrawerDescription>Inline sm absolute column.</DrawerDescription>
              </DrawerHeaderContent>
            </DrawerHeaderStart>
            <DrawerHeaderActions side="end">
              <DrawerClose
                render={
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="Close"
                    icon={<Icon icon={CloseOutline} />}
                  />
                }
              />
            </DrawerHeaderActions>
          </DrawerHeader>
          <DrawerBody>Sidebar content goes here.</DrawerBody>
          <DrawerFooter>
            <DrawerClose
              render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
            />
            <Button stretch="auto" variant="primary" size="md" label="Save" />
          </DrawerFooter>
        </DrawerPanel>
      </Drawer>
    </div>
  );
}
```
## Examples

Use cases map to `variant` + `size`. Prefer **backdrop off** in demos (see [Backdrop](#backdrop)
for the opt-in). Prefer **`sm` + `inline`** (absolute flush column in a `relative` parent — does
not push siblings) unless the example is deliberately about a floating card in a parent shell
([Contained](#contained)).

| Use case         | `variant`   | `size`      | Placement                               |
| ---------------- | ----------- | ----------- | --------------------------------------- |
| Default sidebar  | `inline`    | `sm`        | Absolute in `relative` parent (no push) |
| Floating details | `overlay`   | `md` / `lg` | `document.body` (inset card)            |
| Under app chrome | `contained` | `md` / `lg` | Positioned parent `container`           |

### Overlay

Floating card over the app (`document.body`). No backdrop.

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerFooter,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerPanel,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import {
  ArrowLeftOutline,
  ArrowExpandOutline,
  MoreHorizontalOutline,
  CloseOutline,
} from "@makeplane/propel/icons";

/** Floating card on `document.body` — `md` / `lg`, backdrop off. */
export default function OverlayDemo() {
  return (
    <Drawer swipeDirection="right">
      <DrawerTrigger
        render={<Button stretch="auto" variant="secondary" size="lg" label="Open overlay" />}
      />
      <DrawerPanel variant="overlay" side="end" size="md" aria-label="Work item details">
        <DrawerHeader>
          <DrawerHeaderActions side="start">
            <IconButton
              variant="ghost"
              size="md"
              aria-label="Back"
              icon={<Icon icon={ArrowLeftOutline} />}
            />
            <IconButton
              variant="ghost"
              size="md"
              aria-label="Expand"
              icon={<Icon icon={ArrowExpandOutline} />}
            />
            <IconButton
              variant="ghost"
              size="md"
              aria-label="More"
              icon={<Icon icon={MoreHorizontalOutline} />}
            />
          </DrawerHeaderActions>
          <DrawerHeaderActions side="end">
            <IconButton
              variant="ghost"
              size="md"
              aria-label="More actions"
              icon={<Icon icon={MoreHorizontalOutline} />}
            />
            <DrawerClose
              render={
                <IconButton
                  variant="ghost"
                  size="md"
                  aria-label="Close"
                  icon={<Icon icon={CloseOutline} />}
                />
              }
            />
          </DrawerHeaderActions>
        </DrawerHeader>
        <DrawerBody>Panel body content goes here.</DrawerBody>
        <DrawerFooter>
          <DrawerClose
            render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
          />
          <Button stretch="auto" variant="secondary" size="md" label="Secondary" />
          <Button stretch="auto" variant="primary" size="md" label="Save" />
        </DrawerFooter>
      </DrawerPanel>
    </Drawer>
  );
}
```

### Contained

Floating card portaled into a positioned parent (e.g. main shell under a search topnav). Does not
cover chrome outside that shell.

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerPanel,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ArrowLeftOutline, CloseOutline } from "@makeplane/propel/icons";
import * as React from "react";

/** Floating card portaled into a parent shell (under topnav) — not the page body. */
export default function ContainedDemo() {
  const shellRef = React.useRef<HTMLDivElement>(null);
  return (
    <div className="flex h-[28rem] w-full flex-col overflow-hidden rounded-xl border border-subtle">
      <div className="flex h-11 shrink-0 items-center border-b border-subtle px-4 text-body-sm-medium text-secondary">
        Topnav / search
      </div>
      <div ref={shellRef} className="relative min-h-0 flex-1 overflow-hidden bg-layer-2">
        <Drawer swipeDirection="right">
          <div className="flex flex-col gap-3 p-4">
            <DrawerTrigger
              render={<Button stretch="auto" variant="secondary" size="lg" label="Open panel" />}
            />
            <p className="text-body-sm-regular text-secondary">
              Panel portals into this parent shell only — below the topnav, no page scrim.
            </p>
          </div>
          <DrawerPanel
            variant="contained"
            container={shellRef}
            side="end"
            size="md"
            aria-label="Work item details"
          >
            <DrawerHeader>
              <DrawerHeaderActions side="start">
                <IconButton
                  variant="ghost"
                  size="md"
                  aria-label="Back"
                  icon={<Icon icon={ArrowLeftOutline} />}
                />
              </DrawerHeaderActions>
              <DrawerHeaderActions side="end">
                <DrawerClose
                  render={
                    <IconButton
                      variant="ghost"
                      size="md"
                      aria-label="Close"
                      icon={<Icon icon={CloseOutline} />}
                    />
                  }
                />
              </DrawerHeaderActions>
            </DrawerHeader>
            <DrawerBody>Panel body content goes here.</DrawerBody>
          </DrawerPanel>
        </Drawer>
      </div>
    </div>
  );
}
```

### Side

`side` anchors the panel to the inline-start or inline-end edge. `sm` stays an absolute flush
column inside the relative parent.

Pair `Drawer` `swipeDirection` with `side` so swipe-to-dismiss matches the edge (Base UI defaults
to `"down"`):

| `side`  | `swipeDirection` |
| ------- | ---------------- |
| `end`   | `right`          |
| `start` | `left`           |

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerHeaderContent,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { CloseOutline } from "@makeplane/propel/icons";

/** `sm` inline column on the start edge — absolute in a relative parent. */
export default function StartSideDemo() {
  return (
    <div className="relative h-[28rem] w-full overflow-hidden rounded-xl border border-subtle bg-layer-2">
      <Drawer swipeDirection="left">
        <div className="flex h-full items-center justify-center p-4">
          <DrawerTrigger
            render={<Button stretch="auto" variant="secondary" size="lg" label="Open navigation" />}
          />
        </div>
        <DrawerPanel variant="inline" side="start" size="sm">
          <DrawerHeader>
            <DrawerHeaderStart>
              <DrawerHeaderContent>
                <DrawerTitle>Navigation</DrawerTitle>
              </DrawerHeaderContent>
            </DrawerHeaderStart>
            <DrawerHeaderActions side="end">
              <DrawerClose
                render={
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="Close"
                    icon={<Icon icon={CloseOutline} />}
                  />
                }
              />
            </DrawerHeaderActions>
          </DrawerHeader>
          <DrawerBody>Navigation links go here.</DrawerBody>
        </DrawerPanel>
      </Drawer>
    </div>
  );
}
```

### Size

`sm` 358px (inline absolute column), `md` 800px / `lg` 1024px (overlay cards). Header sockets:
title cluster on sm; icon toolbars on md/lg (`aria-label` the panel when there is no
`DrawerTitle`).

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerHeaderContent,
  DrawerHeaderIcon,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import {
  ArrowLeftOutline,
  CubeOutline,
  ArrowExpandOutline,
  MoreHorizontalOutline,
  CloseOutline,
} from "@makeplane/propel/icons";

/** `sm` = inline absolute column; `md` / `lg` = overlay (backdrop off). */
export default function SizesDemo() {
  return (
    <div className="flex w-full flex-col gap-6">
      <div className="relative h-[28rem] w-full overflow-hidden rounded-xl border border-subtle bg-layer-2">
        <Drawer swipeDirection="right">
          <div className="flex h-full items-center justify-center p-4">
            <DrawerTrigger
              render={<Button stretch="auto" variant="secondary" size="lg" label="Open sm" />}
            />
          </div>
          <DrawerPanel variant="inline" side="end" size="sm">
            <DrawerHeader>
              <DrawerHeaderStart>
                <DrawerHeaderIcon>
                  <CubeOutline />
                </DrawerHeaderIcon>
                <DrawerHeaderContent>
                  <DrawerTitle>Size sm</DrawerTitle>
                  <DrawerDescription>Inline column, 358px.</DrawerDescription>
                </DrawerHeaderContent>
              </DrawerHeaderStart>
              <DrawerHeaderActions side="end">
                <DrawerClose
                  render={
                    <IconButton
                      variant="ghost"
                      size="md"
                      aria-label="Close"
                      icon={<Icon icon={CloseOutline} />}
                    />
                  }
                />
              </DrawerHeaderActions>
            </DrawerHeader>
            <DrawerBody>Panel body content goes here.</DrawerBody>
            <DrawerFooter>
              <DrawerClose
                render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
              />
              <Button stretch="auto" variant="primary" size="md" label="Save" />
            </DrawerFooter>
          </DrawerPanel>
        </Drawer>
      </div>
      <div className="flex flex-wrap gap-2">
        {(["md", "lg"] as const).map((size) => (
          <Drawer key={size} swipeDirection="right">
            <DrawerTrigger
              render={
                <Button stretch="auto" variant="secondary" size="lg" label={`Open ${size}`} />
              }
            />
            <DrawerPanel variant="overlay" side="end" size={size} aria-label={`Size ${size}`}>
              <DrawerHeader>
                <DrawerHeaderActions side="start">
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="Back"
                    icon={<Icon icon={ArrowLeftOutline} />}
                  />
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="Expand"
                    icon={<Icon icon={ArrowExpandOutline} />}
                  />
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="More"
                    icon={<Icon icon={MoreHorizontalOutline} />}
                  />
                </DrawerHeaderActions>
                <DrawerHeaderActions side="end">
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="More actions"
                    icon={<Icon icon={MoreHorizontalOutline} />}
                  />
                  <DrawerClose
                    render={
                      <IconButton
                        variant="ghost"
                        size="md"
                        aria-label="Close"
                        icon={<Icon icon={CloseOutline} />}
                      />
                    }
                  />
                </DrawerHeaderActions>
              </DrawerHeader>
              <DrawerBody>Panel body content goes here.</DrawerBody>
              <DrawerFooter>
                <DrawerClose
                  render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
                />
                <Button stretch="auto" variant="secondary" size="md" label="Secondary" />
                <Button stretch="auto" variant="primary" size="md" label="Save" />
              </DrawerFooter>
            </DrawerPanel>
          </Drawer>
        ))}
      </div>
    </div>
  );
}
```

### Backdrop

`backdrop` is opt-in (default off) and **overlay-only** — not available on `inline` or
`contained`. Pair `<Drawer modal>` with `backdrop` when the page should block behind an overlay
panel.

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerFooter,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerPanel,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ArrowLeftOutline, CloseOutline } from "@makeplane/propel/icons";

export default function BackdropDemo() {
  return (
    <Drawer modal swipeDirection="right">
      <DrawerTrigger
        render={<Button stretch="auto" variant="secondary" size="lg" label="Open with backdrop" />}
      />
      <DrawerPanel variant="overlay" backdrop side="end" size="md" aria-label="With backdrop">
        <DrawerHeader>
          <DrawerHeaderActions side="start">
            <IconButton
              variant="ghost"
              size="md"
              aria-label="Back"
              icon={<Icon icon={ArrowLeftOutline} />}
            />
          </DrawerHeaderActions>
          <DrawerHeaderActions side="end">
            <DrawerClose
              render={
                <IconButton
                  variant="ghost"
                  size="md"
                  aria-label="Close"
                  icon={<Icon icon={CloseOutline} />}
                />
              }
            />
          </DrawerHeaderActions>
        </DrawerHeader>
        <DrawerBody>Backdrop dims the page behind the panel.</DrawerBody>
        <DrawerFooter>
          <DrawerClose
            render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
          />
          <Button stretch="auto" variant="primary" size="md" label="Save" />
        </DrawerFooter>
      </DrawerPanel>
    </Drawer>
  );
}
```

### Nested

Stacked overlay drawers (no backdrop).

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerDescription,
  DrawerHeader,
  DrawerHeaderContent,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";

export default function NestedDemo() {
  return (
    <Drawer swipeDirection="right">
      <DrawerTrigger
        render={<Button stretch="auto" variant="secondary" size="lg" label="Open settings" />}
      />
      <DrawerPanel variant="overlay" side="end" size="md">
        <DrawerHeader>
          <DrawerHeaderStart>
            <DrawerHeaderContent>
              <DrawerTitle>Settings</DrawerTitle>
              <DrawerDescription>Workspace-wide preferences.</DrawerDescription>
            </DrawerHeaderContent>
          </DrawerHeaderStart>
        </DrawerHeader>
        <DrawerBody>
          <Drawer swipeDirection="right">
            <DrawerTrigger
              render={
                <Button stretch="auto" variant="secondary" size="md" label="Advanced options" />
              }
            />
            <DrawerPanel variant="overlay" side="end" size="md">
              <DrawerHeader>
                <DrawerHeaderStart>
                  <DrawerHeaderContent>
                    <DrawerTitle>Advanced options</DrawerTitle>
                  </DrawerHeaderContent>
                </DrawerHeaderStart>
              </DrawerHeader>
              <DrawerBody>Nested drawer content.</DrawerBody>
            </DrawerPanel>
          </Drawer>
        </DrawerBody>
      </DrawerPanel>
    </Drawer>
  );
}
```

### Detached triggers

`createDrawerHandle()` links triggers to a `Drawer` defined elsewhere in the tree; each trigger passes a `payload` the drawer renders through function-as-children.

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  createDrawerHandle,
  Drawer,
  DrawerDescription,
  DrawerHeader,
  DrawerHeaderContent,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";

// A handle shared by triggers that live far from the drawer they open — the
// same details panel launched from unrelated corners of the UI, no lifted state.
const detailsDrawer = createDrawerHandle();

export default function DetachedTriggersDemo() {
  return (
    <div className="flex flex-wrap items-center gap-2">
      <DrawerTrigger
        handle={detailsDrawer}
        payload="WEB-101"
        render={<Button stretch="auto" variant="secondary" size="lg" label="WEB-101" />}
      />
      <DrawerTrigger
        handle={detailsDrawer}
        payload="WEB-202"
        render={<Button stretch="auto" variant="secondary" size="lg" label="WEB-202" />}
      />
      <Drawer handle={detailsDrawer} swipeDirection="right">
        {({ payload }) => (
          <DrawerPanel variant="overlay" side="end" size="md">
            <DrawerHeader>
              <DrawerHeaderStart>
                <DrawerHeaderContent>
                  <DrawerTitle>{typeof payload === "string" ? payload : "Details"}</DrawerTitle>
                  <DrawerDescription>Work item details.</DrawerDescription>
                </DrawerHeaderContent>
              </DrawerHeaderStart>
            </DrawerHeader>
          </DrawerPanel>
        )}
      </Drawer>
    </div>
  );
}
```

### Controlled

A controlled `Drawer` can intercept its own dismissal — here a draft raises a confirmation via `eventDetails.cancel()` before the panel closes.

```tsx
import {
  AlertDialog,
  AlertDialogActions,
  AlertDialogBody,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogHeader,
  AlertDialogIntro,
  AlertDialogTitle,
} from "@makeplane/propel/components/alert-dialog";
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerDescription,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerHeaderContent,
  DrawerHeaderStart,
  DrawerPanel,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { TextArea } from "@makeplane/propel/components/text-area";
import { CloseOutline } from "@makeplane/propel/icons";
import * as React from "react";

export default function ControlledDemo() {
  const [open, setOpen] = React.useState(false);
  const [confirmOpen, setConfirmOpen] = React.useState(false);
  const [draft, setDraft] = React.useState("");
  return (
    <>
      <Drawer
        swipeDirection="right"
        open={open}
        onOpenChange={(nextOpen, eventDetails) => {
          if (!nextOpen && draft.trim() !== "") {
            // Block the dismissal and ask for confirmation while a draft exists.
            eventDetails.cancel();
            setConfirmOpen(true);
            return;
          }
          setOpen(nextOpen);
        }}
      >
        <DrawerTrigger
          render={<Button stretch="auto" variant="secondary" size="lg" label="Add comment" />}
        />
        <DrawerPanel variant="overlay" side="end" size="md">
          <DrawerHeader>
            <DrawerHeaderStart>
              <DrawerHeaderContent>
                <DrawerTitle>New comment</DrawerTitle>
                <DrawerDescription>Share feedback on this work item.</DrawerDescription>
              </DrawerHeaderContent>
            </DrawerHeaderStart>
            <DrawerHeaderActions side="end">
              <DrawerClose
                render={
                  <IconButton
                    variant="ghost"
                    size="md"
                    aria-label="Close"
                    icon={<Icon icon={CloseOutline} />}
                  />
                }
              />
            </DrawerHeaderActions>
          </DrawerHeader>
          <DrawerBody>
            <TextArea
              size="xl"
              surface="field"
              rows={4}
              aria-label="Comment"
              value={draft}
              onChange={(event) => setDraft(event.target.value)}
            />
          </DrawerBody>
        </DrawerPanel>
      </Drawer>
      <AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
        <AlertDialogContent>
          <AlertDialogBody>
            <AlertDialogHeader>
              <AlertDialogIntro>
                <AlertDialogTitle>Discard comment?</AlertDialogTitle>
                <AlertDialogDescription>
                  Your draft will be lost if you close the panel now.
                </AlertDialogDescription>
              </AlertDialogIntro>
            </AlertDialogHeader>
          </AlertDialogBody>
          <AlertDialogActions>
            <Button
              stretch="auto"
              variant="ghost"
              size="md"
              onClick={() => setConfirmOpen(false)}
              label="Keep editing"
            />
            <Button
              stretch="auto"
              variant="danger"
              size="md"
              onClick={() => {
                setConfirmOpen(false);
                setOpen(false);
                setDraft("");
              }}
              label="Discard"
            />
          </AlertDialogActions>
        </AlertDialogContent>
      </AlertDialog>
    </>
  );
}
```

### Page shift

Wrap the page in a `DrawerProvider` with a `DrawerIndentBackground` before a `DrawerIndent` around the content and the `Drawer`. While any drawer in the provider is open, both indent parts carry `data-active`, so the page can scale inward and reveal the background behind it.

```tsx
import { Button } from "@makeplane/propel/components/button";
import {
  Drawer,
  DrawerBody,
  DrawerClose,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerHeaderActions,
  DrawerHeaderContent,
  DrawerHeaderStart,
  DrawerIndent,
  DrawerIndentBackground,
  DrawerPanel,
  DrawerProvider,
  DrawerTitle,
  DrawerTrigger,
} from "@makeplane/propel/components/drawer";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { CloseOutline } from "@makeplane/propel/icons";

export default function PageShiftDemo() {
  return (
    <DrawerProvider>
      <div className="relative overflow-hidden rounded-xl border border-subtle-1">
        <DrawerIndentBackground render={<div className="absolute inset-0 bg-layer-2" />} />
        <DrawerIndent
          render={
            <div className="relative bg-layer-1 p-4 transition-transform duration-300 data-active:scale-[0.98] data-active:rounded-xl" />
          }
        >
          <div className="flex flex-wrap items-center gap-3">
            <Drawer swipeDirection="right">
              <DrawerTrigger
                render={
                  <Button stretch="auto" variant="secondary" size="lg" label="Open details" />
                }
              />
              <DrawerPanel variant="overlay" side="end" size="md">
                <DrawerHeader>
                  <DrawerHeaderStart>
                    <DrawerHeaderContent>
                      <DrawerTitle>Work item details</DrawerTitle>
                      <DrawerDescription>
                        The page indents while this drawer is open.
                      </DrawerDescription>
                    </DrawerHeaderContent>
                  </DrawerHeaderStart>
                  <DrawerHeaderActions side="end">
                    <DrawerClose
                      render={
                        <IconButton
                          variant="ghost"
                          size="md"
                          aria-label="Close"
                          icon={<Icon icon={CloseOutline} />}
                        />
                      }
                    />
                  </DrawerHeaderActions>
                </DrawerHeader>
                <DrawerBody>Panel body content goes here.</DrawerBody>
                <DrawerFooter>
                  <DrawerClose
                    render={<Button stretch="auto" variant="ghost" size="md" label="Cancel" />}
                  />
                  <Button stretch="auto" variant="secondary" size="md" label="Secondary" />
                  <Button stretch="auto" variant="primary" size="md" label="Save" />
                </DrawerFooter>
              </DrawerPanel>
            </Drawer>
            <p className="text-body-sm-regular text-secondary">
              Open the drawer to indent this page.
            </p>
          </div>
        </DrawerIndent>
      </div>
    </DrawerProvider>
  );
}
```
## API Reference

### Drawer

The drawer Root that holds open state and gesture configuration. Uncontrolled by default; pass
`defaultOpen` (uncontrolled) or `open` + `onOpenChange` (controlled).

A behavior-only role that renders no element of its own (rules 1a, 2), so it lives in
`components`; the styled parts live in `elements/drawer` and are grafted onto Base UI behavior
here.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode \| PayloadChildRenderFunction<unknown>` | — | The content of the drawer. |
| `modal` | `boolean \| "trap-focus"` | `false` | Modal while open. Off by default so inline/layout drawers keep the page interactive. Pass `true` or `"trap-focus"` for blocking overlays (typically with `DrawerPanel backdrop`). |
| `swipeDirection` | `"up" \| "down" \| "left" \| "right"` | — | Swipe-to-dismiss direction. Pair with `DrawerPanel` `side`: `"end"` → `"right"`, `"start"` → `"left"`. Base UI defaults this to `"down"` (vertical), which is wrong for horizontal edge drawers — set it explicitly. |

### DrawerTrigger

The behavior that opens the drawer. A behavior-only role that carries no propel styling, so graft
it onto a styled control via `render` — behavior part outer, styled part as the render target, so
the control's look wins:

```tsx
<DrawerTrigger
  render={<Button stretch="auto" variant="secondary" size="lg" label="Open details" />}
/>;
```

Base UI manages `aria-haspopup`/`aria-expanded` and focus restoration when the drawer closes.
Maps 1:1 to `Drawer.Trigger`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerTriggerState>` | — | 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. |

### DrawerPanel

Convenience that composes drawer portal/viewport/popup boilerplate. Defaults to `inline` + `sm`
(flush absolute column in a relative parent). `overlay` portals to `document.body`; `contained`
requires a parent `container`. `backdrop` is overlay-only.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` (required) | `"end" \| "start"` | — | The viewport edge the drawer is anchored to. "end" pins to the inline-end (right in LTR); "start" pins to the inline-start (left in LTR). The slide animation direction and the leading-edge chrome both follow this value. Pair with `Drawer` `swipeDirection`: `"end"` → `"right"`, `"start"` → `"left"`. |
| `size` | `"sm" \| "md" \| "lg"` | `"sm"` | Panel width: `sm` 358px, `md` 800px, `lg` 1024px. Prefer `sm` with `inline`; `md` / `lg` with `overlay` / `contained`. |
| `children` | `ReactNode` | — | The panel body — typically a `DrawerHeader`, `DrawerBody`, and `DrawerFooter`. |
| `variant` | `"inline" \| "overlay" \| "contained"` | `"inline"` | Placement + chrome. Defaults to the flush absolute column (`inline`). Floating card portaled to `document.body` (viewport-fixed). Prefer `md` / `lg`. Floating card portaled into a positioned parent shell (`container` required). |
| `container` | `DrawerPortalContainer \| null` | — | Portal target. When omitted, uses an absolute-fill host (parent must be `relative`). Pass a positioned shell when you manage the host yourself. Portal target. When omitted, uses `document.body`. Positioned parent the floating card portals into (required for `contained`). |
| `backdrop` | `boolean` | `false` | When true, mounts a dimmed viewport-fixed `DrawerBackdrop`. Only available on `overlay` (pair with `<Drawer modal>`). |

### DrawerBackdrop

The dimmed overlay behind the drawer: Base UI's `Drawer.Backdrop` behavior grafted onto the
shared `internal/Backdrop` surface (rule 4a). Fades in/out via Base UI's
`data-starting-style`/`data-ending-style` transition hooks.

Nested drawers keep their own dim (`forceRender` defaults on): Base UI omits a nested Backdrop
unless it is set. Pass `forceRender={false}` to opt out.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerBackdropState>` | — | 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. |

### DrawerTitle

The accessible heading for the drawer: Base UI's `Drawer.Title` behavior (wired as the popup's
`aria-labelledby` target) grafted onto the shared `internal/OverlayTitle` at `size="xl"` (body-md
semibold per Figma; rule 4a).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerTitleState>` | — | 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

Supporting text for the drawer: Base UI's `Drawer.Description` behavior (wired as the popup's
`aria-describedby` target) grafted onto the shared `internal/OverlayDescription` at `size="md"`
(body-xs per Figma; rule 4a).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerDescriptionState>` | — | 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

The drawer's top region (the Figma header). Compose a start cluster (`DrawerHeaderStart` or
`DrawerHeaderActions side="start"`) and an end cluster (`DrawerHeaderActions side="end"`) for the
title/icon sockets.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerHeaderStart

The header's inline-start title cluster (Figma narrow header). Holds an optional
`DrawerHeaderIcon` / `IconButton` plus `DrawerHeaderContent` (title + description).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerHeaderIcon

Decorative leading glyph beside the title inside `DrawerHeaderStart`. Sizes a bare svg/img child;
interactive leading controls should be an `IconButton` instead. Bakes `aria-hidden`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerHeaderContent

The stacked title + description inside a `DrawerHeader`. Groups them at the header's inline-start
so a corner close can sit opposite at the inline-end; a long title wraps instead of pushing the
close off the row.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerHeaderActions

Header icon-button toolbar. `side="start"` / `side="end"` pin the cluster to the header's inline
edges (Figma top-left / top-right sockets). Pass `IconButton`s and/or `DrawerClose`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` (required) | `"start" \| "end"` | — |  |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerBody

The drawer's main content region (the Figma body). Grows to fill the space between the header and
footer and scrolls its own overflow.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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. |

### DrawerFooter

The drawer's footer actions region (the Figma footer). Lays its actions at the inline-end.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | 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

The behavior that closes the drawer when activated. A behavior-only role that carries no propel
styling, so graft it onto a styled control via `render` — behavior part outer, styled part as the
render target, so the control's look wins:

```tsx
<DrawerClose
  render={
    <IconButton variant="ghost" size="md" aria-label={closeLabel} icon={<Icon icon={X} />} />
  }
/>;
```

Maps 1:1 to `Drawer.Close`. (The styled bare close control it replaces in this entry still lives
at `elements/drawer` for assembling a drawer from the atoms.)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerCloseState>` | — | 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. |

### DrawerProvider

Optional context wrapper that coordinates indent effects across sibling drawers. Wrap `Drawer`
(and any `DrawerIndent`/`DrawerIndentBackground`) in it when you need the page to shift as the
drawer opens. A behavior-only provider role (rules 1a, 2), so it lives in `components`. Maps 1:1
to Base UI's `Drawer.Provider`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | — | The `Drawer`(s) and any `DrawerIndent`/`DrawerIndentBackground` to coordinate. |

### DrawerIndent

Wraps page content that should scale/shift inward as the drawer opens (the indent effect). Place
inside a `DrawerProvider`, around the `Drawer` root and your page. A behavior-only role (rules
1a, 2), so it lives in `components`. Maps 1:1 to Base UI's `Drawer.Indent`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerIndentState>` | — | 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. |

### DrawerIndentBackground

Renders behind the indented page content so the area revealed by the indent effect has a backdrop
color. Used alongside `DrawerIndent` inside a `DrawerProvider`. A behavior-only role (rules 1a,
2), so it lives in `components`. Maps 1:1 to Base UI's `Drawer.IndentBackground`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerIndentBackgroundState>` | — | 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. |

### DrawerSwipeArea

An off-screen edge hit area that lets users swipe the closed drawer open from the screen edge.
Optional; render it as a sibling of the trigger. A behavior-only role (rules 1a, 2), so it lives
in `components`. Maps 1:1 to Base UI's `Drawer.SwipeArea`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, DrawerSwipeAreaState>` | — | 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. |
