# Drawer (/docs/drawer)





## API Reference [#api-reference]

<BaseUIReference href="https://base-ui.com/react/components/drawer" />

## When to use Drawer [#when-to-use-drawer]

Use `Drawer` as the default choice for edge-attached panels in moduix.
It covers both patterns that are often split in other libraries:

* **Drawer behavior:** edge placement, swipe-to-dismiss, optional edge swipe area, and snap points.
* **Sheet behavior:** structured panel content (header/body/footer), side and bottom layouts, and
  non-modal/persistent configurations.

If a panel does not need gestures or snap points, prefer `Dialog` with edge positioning.

## Basic [#basic]

<Preview cssProperties="drawerPlaygroundCssProperties">
  <DrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerBody,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function DrawerDemo() {
            return (
              <Drawer>
                <DrawerTrigger render={<Button />}>Open bottom drawer</DrawerTrigger>
                <DrawerContent>
                  <DrawerHeader>
                    <DrawerTitle>Notifications</DrawerTitle>
                    <DrawerDescription>You are all caught up. Good job!</DrawerDescription>
                  </DrawerHeader>
                  <DrawerBody>Bottom drawers are the default.</DrawerBody>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSSProperties>
    {(context) => <DrawerCssPropertiesPanel {...context} />}
  </Preview.CSSProperties>

  <Preview.CSSPlayground>
    {(context) => <DrawerCssPlaygroundPanel {...context} />}
  </Preview.CSSPlayground>
</Preview>

## Anatomy [#anatomy]

`Drawer` combines root state/gesture behavior with one visible panel and internal service slots.
Use `DrawerContent` as the composed popup container. It renders `portal`, `backdrop`, `viewport`,
`handle`, and the inner `content` slot for you.

```text
Drawer
├─ DrawerTrigger (optional)
└─ DrawerContent
   ├─ portal (service slot)
   │  ├─ backdrop (service slot, optional)
   │  └─ viewport (service slot)
   │     └─ popup surface
   │        ├─ handle (service slot, optional)
   │        └─ content (service slot)
   │           ├─ DrawerHeader
   │           │  ├─ DrawerTitle
   │           │  └─ DrawerDescription (optional)
   │           ├─ DrawerBody (optional)
   │           └─ DrawerFooter (optional)
   │              └─ DrawerClose
```

```tsx
<Drawer>
  <DrawerTrigger render={<Button />}>Open drawer</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Notifications</DrawerTitle>
      <DrawerDescription>You are all caught up. Good job!</DrawerDescription>
    </DrawerHeader>
    <DrawerBody>Bottom drawers are the default.</DrawerBody>
    <DrawerFooter>
      <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>
```

| Part                | Role                                                                                                 |
| ------------------- | ---------------------------------------------------------------------------------------------------- |
| `Drawer`            | Root state and gesture model. Use `open`, `defaultOpen`, `onOpenChange`, `modal`, and snap props.    |
| `DrawerTrigger`     | Opens the drawer from user interaction. Use `handle` when trigger logic is managed outside the tree. |
| `DrawerContent`     | Popup container. Renders internal `portal`, `backdrop`, `viewport`, `handle`, and `content` slots.   |
| `DrawerHeader`      | Groups title-level content at the top of the panel.                                                  |
| `DrawerTitle`       | Accessible drawer title announced by assistive technology.                                           |
| `DrawerDescription` | Optional supporting text announced with the title.                                                   |
| `DrawerBody`        | Optional content region for forms, lists, and long scrollable content.                               |
| `DrawerFooter`      | Optional actions row for close, submit, and secondary actions.                                       |
| `DrawerClose`       | Closes the drawer from action buttons while preserving built-in behavior.                            |

In most cases, keep default layering and gestures and style visible panel parts with `className`
(`DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`). Customize internal service slots with
`classNames` (`portal`, `backdrop`, `viewport`, `handle`, `content`) only when you need special
positioning, overlay, or handle behavior.

## Composition [#composition]

Use `Drawer` for root state and behavior props such as `open`, `defaultOpen`, `onOpenChange`,
`modal`, `swipeDirection`, `snapPoints`, `snapPoint`, and `onSnapPointChange`. Use `DrawerContent`
for the default composed surface. Its `className` and regular popup props are passed to
the visible sliding panel.

Use `withBackdrop`, `withHandle`, `variant`, `snapLayout`, and `container` for common behavior.

`className` styles the visible popup. `classNames` styles the internal slots hidden from the default
composition. Use `slotProps` only when those internal slots need non-class props from Base UI:

```tsx
<DrawerContent
  className={styles.popup}
  classNames={{
    portal: styles.portal,
    backdrop: styles.backdrop,
    viewport: styles.viewport,
    handle: styles.handle,
    content: styles.content,
  }}
  slotProps={{
    portal: { keepMounted: true },
    backdrop: { forceRender: true },
    viewport: { render: <div /> },
    handle: { 'aria-hidden': true },
  }}
/>
```

## Examples [#examples]

### Top [#top]

Use `swipeDirection="up"` for a drawer attached to the top edge.

<Preview>
  <TopDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function TopDrawerDemo() {
            return (
              <Drawer swipeDirection="up">
                <DrawerTrigger render={<Button />}>Open top drawer</DrawerTrigger>
                <DrawerContent>
                  <DrawerHeader>
                    <DrawerTitle>Top panel</DrawerTitle>
                    <DrawerDescription>Set swipeDirection to up for a top drawer.</DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Left [#left]

Use `swipeDirection="left"` for left-side navigation, filters, or mobile panels.

<Preview>
  <LeftDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerBody,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function LeftDrawerDemo() {
            return (
              <Drawer swipeDirection="left">
                <DrawerTrigger render={<Button />}>Open left drawer</DrawerTrigger>
                <DrawerContent className={styles.sideContent}>
                  <DrawerHeader>
                    <DrawerTitle>Filters</DrawerTitle>
                    <DrawerDescription>Side drawers use the same composition.</DrawerDescription>
                  </DrawerHeader>
                  <DrawerBody>Use side drawers for filters, navigation, or contextual panels.</DrawerBody>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .sideContent {
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Right [#right]

Use `swipeDirection="right"` for inspectors, details, and secondary workflows.
Side drawers are full-height by default. Set `--drawer-side-height` to a fixed length,
percentage, `auto`, or `max-content` when a side drawer should be shorter or fit its content.
Use `--drawer-side-max-height` to cap long content and keep the panel inside the viewport.

<Preview>
  <RightDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function RightDrawerDemo() {
            return (
              <Drawer swipeDirection="right">
                <DrawerTrigger render={<Button />}>Open right drawer</DrawerTrigger>
                <DrawerContent className={styles.sideContent}>
                  <DrawerHeader>
                    <DrawerTitle>Details</DrawerTitle>
                    <DrawerDescription>Set width through CSS variables or className.</DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .sideContent {
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Snap Points [#snap-points]

Pass `snapPoints`, `snapPoint`, and `onSnapPointChange` when the drawer should settle at preset
heights. `snapPoints` is the list of allowed positions. `snapPoint` is the currently active
position when controlled. `onSnapPointChange` receives the next active position. Set `snapLayout`
on `DrawerContent` when the content should visually resize with the active snap point. It does not
enable snapping by itself; it applies the library's snap-aware layout styles for the popup tail,
drag offset, and inner content height.

<Preview>
  <SnapPointsDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerBody,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            ScrollArea,
            DrawerTrigger,
          } from "moduix";
          import { useState } from "react";

          const releaseSections = [
            {
              title: "Migration",
              body: "Verify migration scripts, rollback steps, and staging smoke tests.",
            },
            {
              title: "Monitoring",
              body: "Check dashboards, alerts, and post-release health checks.",
            },
            {
              title: "Rollout",
              body: "Confirm feature flags, analytics events, and support notes.",
            },
          ];

          export function SnapPointsDrawerDemo() {
            const snapPoints = [0.35, 0.65, 1];
            const [snapPoint, setSnapPoint] = useState(snapPoints[1] as number | string | null);

            return (
              <Drawer snapPoints={snapPoints} snapPoint={snapPoint} onSnapPointChange={setSnapPoint}>
                <DrawerTrigger render={<Button />}>Open snap drawer</DrawerTrigger>
                <DrawerContent snapLayout>
                  <DrawerHeader>
                    <DrawerTitle>Release checklist</DrawerTitle>
                    <DrawerDescription>Current snap point: {String(snapPoint)}</DrawerDescription>
                  </DrawerHeader>
                  <DrawerBody>
                    <ScrollArea
                      className={styles.scrollArea}
                      classNames={{ content: styles.scrollContent }}
                    >
                      {releaseSections.map((item) => (
                        <section key={item.title}>
                          <h3>{item.title}</h3>
                          <p>{item.body}</p>
                        </section>
                      ))}
                    </ScrollArea>
                  </DrawerBody>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .scrollArea {
            height: 100%;
            min-height: 0;
          }

          .scrollContent {
            display: grid;
            gap: var(--spacing-4);
            padding-right: var(--spacing-5);
          }
        `}
  </Preview.CSS>
</Preview>

### Without Backdrop [#without-backdrop]

Set `withBackdrop={false}` when the drawer should not render an overlay.

<Preview>
  <WithoutBackdropDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function WithoutBackdropDrawerDemo() {
            return (
              <Drawer>
                <DrawerTrigger render={<Button />}>Open without backdrop</DrawerTrigger>
                <DrawerContent withBackdrop={false}>
                  <DrawerHeader>
                    <DrawerTitle>No backdrop</DrawerTitle>
                    <DrawerDescription>Use this when the page should stay visually available.</DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Nested [#nested]

Drawers can be nested. Base UI exposes nested-drawer state attributes and CSS variables to style the
stack.

<Preview>
  <NestedDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerClose,
            DrawerContent,
            DrawerDescription,
            DrawerFooter,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function NestedDrawerDemo() {
            return (
              <Drawer>
                <DrawerTrigger render={<Button />}>Open drawer stack</DrawerTrigger>
                <DrawerContent>
                  <DrawerHeader>
                    <DrawerTitle>Account</DrawerTitle>
                    <DrawerDescription>Nested drawers visually recede while the child is active.</DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <div className={styles.nestedActionsStart}>
                      <Drawer>
                        <DrawerTrigger render={<Button />}>Open nested</DrawerTrigger>
                        <DrawerContent>
                          <DrawerHeader>
                            <DrawerTitle>Nested drawer</DrawerTitle>
                            <DrawerDescription>Second layer in the stack.</DrawerDescription>
                          </DrawerHeader>
                          <DrawerFooter>
                            <DrawerClose render={<Button variant="outline" />}>Close nested</DrawerClose>
                          </DrawerFooter>
                        </DrawerContent>
                      </Drawer>
                    </div>
                    <DrawerClose render={<Button variant="outline" />}>Close root</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .nestedActionsStart {
            margin-right: auto;
          }
        `}
  </Preview.CSS>
</Preview>

### Bottom Island [#bottom-island]

Use `variant="island"` to render the popup inset from the viewport instead of bleeding off-screen.

<Preview>
  <BottomIslandDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function BottomIslandDrawerDemo() {
            return (
              <Drawer>
                <DrawerTrigger render={<Button />}>Open bottom island</DrawerTrigger>
                <DrawerContent variant="island" className={styles.islandContent}>
                  <DrawerHeader>
                    <DrawerTitle>Bottom island</DrawerTitle>
                    <DrawerDescription>Island drawers remove the bleed tail.</DrawerDescription>
                  </DrawerHeader>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .islandContent {
            --drawer-width: 32rem;
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Top Island [#top-island]

The island variant works with top drawers too.

<Preview>
  <TopIslandDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function TopIslandDrawerDemo() {
            return (
              <Drawer swipeDirection="up">
                <DrawerTrigger render={<Button />}>Open top island</DrawerTrigger>
                <DrawerContent variant="island" className={styles.islandContent}>
                  <DrawerHeader>
                    <DrawerTitle>Top island</DrawerTitle>
                    <DrawerDescription>The same variant works from the top edge.</DrawerDescription>
                  </DrawerHeader>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .islandContent {
            --drawer-width: 32rem;
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Left Island [#left-island]

Use side island drawers when the panel should not touch the viewport edge.

<Preview>
  <LeftIslandDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function LeftIslandDrawerDemo() {
            return (
              <Drawer swipeDirection="left">
                <DrawerTrigger render={<Button />}>Open left island</DrawerTrigger>
                <DrawerContent variant="island" className={styles.islandContent}>
                  <DrawerHeader>
                    <DrawerTitle>Left island</DrawerTitle>
                    <DrawerDescription>Side island drawers keep an inset around the viewport.</DrawerDescription>
                  </DrawerHeader>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .islandContent {
            --drawer-width: 32rem;
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Right Island [#right-island]

Customize important slots with `className`, CSS Modules, Tailwind, or CSS-in-JS.

<Preview>
  <RightIslandDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function RightIslandDrawerDemo() {
            return (
              <Drawer swipeDirection="right">
                <DrawerTrigger render={<Button />}>Open right island</DrawerTrigger>
                <DrawerContent variant="island" className={styles.islandContent}>
                  <DrawerHeader>
                    <DrawerTitle>Right island</DrawerTitle>
                    <DrawerDescription>Use className to tune important slots for your layout.</DrawerDescription>
                  </DrawerHeader>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .islandContent {
            --drawer-width: 32rem;
            --drawer-side-width: 26rem;
          }
        `}
  </Preview.CSS>
</Preview>

### Persistent Snap [#persistent-snap]

Combine `persistent`, non-modal behavior, and snap points for persistent bottom panels.
The `persistent` prop prevents drawer-initiated close attempts and ignores `null` snap points.
Place `ScrollArea` inside `DrawerBody` when the header should remain visible while only the body
content scrolls.

<Preview>
  <PersistentSnapDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerHeader,
            DrawerTitle,
            DrawerSnapToggle,
            DrawerDescription,
            DrawerBody,
            ScrollArea,
          } from "moduix";
          import { useState, Fragment } from "react";

          const releaseSections = [
            {
              title: "Migration",
              body: "Verify migration scripts, rollback steps, and staging smoke tests.",
            },
            {
              title: "Monitoring",
              body: "Check dashboards, alerts, and post-release health checks.",
            },
            {
              title: "Rollout",
              body: "Confirm feature flags, analytics events, and support notes.",
            },
          ];

          export function PersistentSnapDrawerDemo() {
            const snapPoints = [0.35, 0.85] as const;
            const [open, setOpen] = useState(false);
            const [snapPoint, setSnapPoint] = useState(snapPoints[0] as number | string | null);
            const expanded = snapPoint === snapPoints[1];

            return (
              <Fragment>
                <Button type="button" onClick={() => setOpen(true)}>
                  Open persistent drawer
                </Button>
                <Drawer
                  open={open}
                  persistent
                  modal={false}
                  disablePointerDismissal
                  snapPoints={[...snapPoints]}
                  defaultSnapPoint={snapPoints[0]}
                  snapPoint={snapPoint}
                  onSnapPointChange={setSnapPoint}
                >
                  <DrawerContent snapLayout withBackdrop={false} disableInitialAnimation>
                    <DrawerHeader>
                      <DrawerTitle>Persistent drawer</DrawerTitle>
                      <DrawerSnapToggle
                        expanded={expanded}
                        onClick={() => setSnapPoint(expanded ? snapPoints[0] : snapPoints[1])}
                      />
                      <DrawerDescription>Switch between compact and expanded states.</DrawerDescription>
                    </DrawerHeader>
                    <DrawerBody>
                      <ScrollArea
                        className={styles.scrollArea}
                        classNames={{ content: styles.scrollContent }}
                      >
                        {releaseSections.map((item) => (
                          <section key={item.title}>
                            <h3>{item.title}</h3>
                            <p>{item.body}</p>
                          </section>
                        ))}
                      </ScrollArea>
                    </DrawerBody>
                  </DrawerContent>
                </Drawer>
              </Fragment>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .scrollArea {
            height: 100%;
            min-height: 0;
          }

          .scrollContent {
            display: grid;
            gap: var(--spacing-4);
            padding-right: var(--spacing-5);
          }
        `}
  </Preview.CSS>
</Preview>

### Swipe Area [#swipe-area]

Place `DrawerSwipeArea` near a viewport edge to enable swipe-to-open gestures.

<Preview>
  <SwipeAreaDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerSwipeArea,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function SwipeAreaDrawerDemo() {
            return (
              <Drawer swipeDirection="right" modal={false}>
                <DrawerSwipeArea />
                <DrawerTrigger render={<Button />}>Open with trigger</DrawerTrigger>
                <DrawerContent withBackdrop={false}>
                  <DrawerHeader>
                    <DrawerTitle>Swipe area</DrawerTitle>
                    <DrawerDescription>Swipe from the left edge or use the trigger.</DrawerDescription>
                  </DrawerHeader>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>
</Preview>

### Indent Effect [#indent-effect]

Wrap the surface with `DrawerProvider`, `DrawerIndentBackground`, and `DrawerIndent` to scale the
page while a drawer is open.

<Preview>
  <IndentEffectDrawerExample />

  <Preview.Code>
    {`
          import {
            Button,
            Drawer,
            DrawerContent,
            DrawerDescription,
            DrawerHeader,
            DrawerProvider,
            DrawerIndentBackground,
            DrawerIndent,
            DrawerTitle,
            DrawerTrigger,
          } from "moduix";

          export function IndentEffectDrawerDemo() {
            return (
              <DrawerProvider>
                <div className={styles.indentStage}>
                  <DrawerIndentBackground />
                  <DrawerIndent className={styles.indentSurface}>
                    <Drawer modal={false}>
                      <DrawerTrigger render={<Button />}>Open indented drawer</DrawerTrigger>
                      <DrawerContent>
                        <DrawerHeader>
                          <DrawerTitle>Indent effect</DrawerTitle>
                          <DrawerDescription>
                            Provider, indent, and background parts react to open drawers.
                          </DrawerDescription>
                        </DrawerHeader>
                      </DrawerContent>
                    </Drawer>
                  </DrawerIndent>
                </div>
              </DrawerProvider>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .indentStage {
            position: relative;
            width: min(42rem, 100%);
            min-height: 360px;
            overflow: hidden;
            border: var(--border-width-sm) solid var(--color-border);
            border-radius: var(--radius-lg);
            background-color: var(--color-muted);
          }

          .indentSurface {
            min-height: 360px;
            padding: var(--spacing-6);
            background-color: var(--color-background);
          }
        `}
  </Preview.CSS>
</Preview>

### Custom Styles [#custom-styles]

Use `className` for the visible popup and `classNames` for the internal slots rendered by
`DrawerContent`. Pass children to `DrawerSnapToggle` to replace the default icon.

<Preview>
  <CustomStylesDrawerExample />

  <Preview.Code>
    {`
          import {
            Drawer,
            DrawerTrigger,
            Button,
            DrawerContent,
            DrawerHeader,
            DrawerTitle,
            DrawerSnapToggle,
            DrawerFooter,
            DrawerClose,
            ChevronDownIcon,
            ChevronUpIcon,
            DrawerDescription,
          } from "moduix";
          import { useState } from "react";

          export function CustomStylesDrawerDemo() {
            const snapPoints = [0.35, 0.75] as const;
            const [snapPoint, setSnapPoint] = useState<number | string | null>(snapPoints[0]);
            const expanded = snapPoint === snapPoints[1];

            return (
              <Drawer snapPoints={[...snapPoints]} snapPoint={snapPoint} onSnapPointChange={setSnapPoint}>
                <DrawerTrigger render={<Button />}>Open custom drawer</DrawerTrigger>
                <DrawerContent
                  snapLayout
                  className={styles.customPopup}
                  classNames={{
                    backdrop: styles.customBackdrop,
                    viewport: styles.customViewport,
                    handle: styles.customHandle,
                    content: styles.customContent,
                  }}
                >
                  <DrawerHeader>
                    <DrawerTitle>Custom styles</DrawerTitle>
                    <DrawerSnapToggle
                      expanded={expanded}
                      onClick={() => setSnapPoint(expanded ? snapPoints[0] : snapPoints[1])}
                    >
                      {expanded ? <ChevronDownIcon /> : <ChevronUpIcon />}
                    </DrawerSnapToggle>
                    <DrawerDescription>
                      Popup styles use className. Internal slots use classNames.
                    </DrawerDescription>
                  </DrawerHeader>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant="outline" />}>Close</DrawerClose>
                  </DrawerFooter>
                </DrawerContent>
              </Drawer>
            );
          }
        `}
  </Preview.Code>

  <Preview.CSS>
    {`
          .customPopup {
            --drawer-bg: var(--color-card);
            --drawer-border-color: var(--color-ring);
            --drawer-shadow: var(--shadow-xl);
            --drawer-max-height: 30rem;
          }

          .customBackdrop {
            --drawer-backdrop-bg: color-mix(in oklab, var(--color-foreground) 60%, transparent);
          }

          .customViewport {
            --drawer-viewport-padding: var(--spacing-4);
          }

          .customHandle {
            --drawer-handle-bg: var(--color-destructive);
            --drawer-handle-opacity: 0.8;
          }

          .customContent {
            gap: var(--spacing-4);
          }
        `}
  </Preview.CSS>
</Preview>
