---
title: Drawer
description: A panel that slides in from the edge of the screen with swipe-to-dismiss gestures.
links:
  doc: https://base-ui.com/react/components/drawer
  api: https://base-ui.com/react/components/drawer#api-reference
---

```tsx
'use client';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

export function DrawerDefault() {
  return (
    <Drawer>
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Drawer
      </DrawerTrigger>
      <DrawerPopup>
        <DrawerContent>
          <DrawerHeader>
            <DrawerTitle>Notifications</DrawerTitle>
            <DrawerDescription>
              You are all caught up. Good job!
            </DrawerDescription>
          </DrawerHeader>
          <DrawerFooter>
            <DrawerClose render={<Button variant='outline' />}>
              Close
            </DrawerClose>
          </DrawerFooter>
        </DrawerContent>
      </DrawerPopup>
    </Drawer>
  );
}
```

## Installation

```bash
npx shadcn@latest add @fab-ui/drawer
```

**Install the following dependencies:**

```bash
npm install @base-ui/react
```

**Copy and paste the following code into your project.**

```tsx
'use client';

import * as React from 'react';

import { Drawer as DrawerPrimitive } from '@base-ui/react/drawer';

import { cn } from '@/lib/utils';

function Drawer({
  swipeDirection = 'down',
  ...props
}: DrawerPrimitive.Root.Props) {
  return (
    <DrawerPrimitive.Root
      data-slot='drawer'
      swipeDirection={swipeDirection}
      {...props}
    />
  );
}

function DrawerTrigger({ ...props }: DrawerPrimitive.Trigger.Props) {
  return <DrawerPrimitive.Trigger data-slot='drawer-trigger' {...props} />;
}

function DrawerClose({ ...props }: DrawerPrimitive.Close.Props) {
  return <DrawerPrimitive.Close data-slot='drawer-close' {...props} />;
}

function DrawerProvider({ ...props }: DrawerPrimitive.Provider.Props) {
  return <DrawerPrimitive.Provider data-slot='drawer-provider' {...props} />;
}

function DrawerPortal({
  className,
  ...props
}: Omit<DrawerPrimitive.Portal.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Portal
      data-slot='drawer-portal'
      className={cn('z-50', className)}
      {...props}
    />
  );
}

function DrawerOverlay({
  className,
  ...props
}: Omit<DrawerPrimitive.Backdrop.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Backdrop
      data-slot='drawer-overlay'
      className={cn(
        'fixed inset-0 min-h-dvh bg-black opacity-[calc(var(--backdrop-opacity)*(1-var(--drawer-swipe-progress)))] transition-opacity duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] [--backdrop-opacity:0.2] data-ending-style:opacity-0 data-ending-style:duration-[calc(var(--drawer-swipe-strength)*300ms)] data-starting-style:opacity-0 data-swiping:duration-0 supports-backdrop-filter:backdrop-blur-3xl supports-[-webkit-touch-callout:none]:absolute dark:[--backdrop-opacity:0.7]',
        className
      )}
      {...props}
    />
  );
}

function DrawerViewport({
  className,
  ...props
}: Omit<DrawerPrimitive.Viewport.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Viewport
      data-slot='drawer-viewport'
      className={cn(
        'fixed inset-0 flex',
        // Horizontal: stretch + viewport padding
        'has-data-[swipe-direction=left]:items-stretch has-data-[swipe-direction=left]:justify-start has-data-[swipe-direction=left]:p-(--viewport-padding)',
        'has-data-[swipe-direction=right]:items-stretch has-data-[swipe-direction=right]:justify-end has-data-[swipe-direction=right]:p-(--viewport-padding)',
        '[--viewport-padding:0px] supports-[-webkit-touch-callout:none]:[--viewport-padding:0.625rem]',
        // Vertical: center horizontally, align to edge
        'has-data-[swipe-direction=down]:items-end has-data-[swipe-direction=down]:justify-center',
        'has-data-[swipe-direction=up]:items-start has-data-[swipe-direction=up]:justify-center',
        className
      )}
      {...props}
    />
  );
}

function DrawerHandle({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot='drawer-handle'
      className={cn(
        'mx-auto mb-5 h-1 w-12 shrink-0 rounded-full bg-muted transition-opacity duration-200 group-data-nested-drawer-open/popup:opacity-0 group-data-nested-drawer-swiping/popup:opacity-100',
        className
      )}
      {...props}
    />
  );
}

function DrawerContent({
  className,
  ...props
}: Omit<DrawerPrimitive.Content.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Content
      data-slot='drawer-content'
      className={cn(
        'transition-opacity duration-300 ease-[cubic-bezier(0.45,1.005,0,1.005)] group-data-nested-drawer-open/popup:opacity-0 group-data-nested-drawer-swiping/popup:opacity-100',
        className
      )}
      {...props}
    />
  );
}

function DrawerPopup({
  className,
  children,
  container,
  showHandle,
  unstyled,
  overlayClassName,
  ...props
}: Omit<DrawerPrimitive.Popup.Props, 'className'> & {
  className?: string;
  container?: DrawerPrimitive.Portal.Props['container'];
  showHandle?: boolean;
  unstyled?: boolean;
  overlayClassName?: string;
}) {
  return (
    <DrawerPortal container={container}>
      <DrawerOverlay className={overlayClassName} />
      <DrawerViewport>
        <DrawerPrimitive.Popup
          data-slot='drawer-popup'
          className={
            unstyled
              ? className
              : cn(
                  'group/popup relative',
                  'touch-auto overflow-y-auto overscroll-contain bg-background text-foreground ring-1 ring-foreground/5 data-swiping:select-none dark:ring-border',
                  'data-ending-style:duration-[calc(var(--drawer-swipe-strength)*300ms)]',
                  // Nested drawer stacking variables (no-ops when not nested)
                  '[--bleed:3rem] [--height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))-var(--bleed)))] [--peek:1rem] [--scale-base:calc(max(0,1-(var(--nested-drawers)*var(--stack-step))))] [--scale:clamp(0,calc(var(--scale-base)+(var(--stack-step)*var(--stack-progress))),1)] [--shrink:calc(1-var(--scale))] [--stack-peek-offset:max(0px,calc((var(--nested-drawers)-var(--stack-progress))*var(--peek)))] [--stack-progress:clamp(0,var(--drawer-swipe-progress),1)] [--stack-step:0.05]',
                  // Nested drawer overlay (::after pseudo-element)
                  "after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-transparent after:transition-[background-color] after:duration-300 after:ease-[cubic-bezier(0.32,0.72,0,1)] after:content-['']",
                  // Nested drawer states
                  'data-nested-drawer-open:overflow-hidden data-nested-drawer-open:after:bg-black/5 data-nested-drawer-swiping:duration-0',
                  // Shared horizontal (left & right)
                  'data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:[--bleed:0px] data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:[--bleed:0px]',
                  'data-[swipe-direction=left]:h-full data-[swipe-direction=right]:h-full',
                  'data-[swipe-direction=left]:w-[calc(22rem+var(--bleed))] data-[swipe-direction=right]:w-[calc(22rem+var(--bleed))]',
                  'data-[swipe-direction=left]:max-w-[calc(100vw-3rem+var(--bleed))] data-[swipe-direction=right]:max-w-[calc(100vw-3rem+var(--bleed))]',
                  'data-[swipe-direction=left]:p-6 data-[swipe-direction=right]:p-6',
                  'data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:w-[20rem] data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:w-[20rem]',
                  'data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:max-w-[calc(100vw-20px)] data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:max-w-[calc(100vw-20px)]',
                  'data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:rounded-[10px] data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:rounded-[10px]',
                  // Right-only (with stacking transform + transition for box-shadow)
                  'data-[swipe-direction=right]:-mr-(--bleed) data-[swipe-direction=right]:origin-[calc(100%-var(--bleed))_50%] data-[swipe-direction=right]:transform-[translateX(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-x)-var(--stack-peek-offset)-(var(--shrink)*100%)))_scale(var(--scale))] data-[swipe-direction=right]:rounded-l-xl data-[swipe-direction=right]:pr-[calc(1.5rem+var(--bleed))] data-[swipe-direction=right]:shadow-[-2px_0_10px_rgb(0_0_0/0.1)] data-[swipe-direction=right]:[transition:transform_300ms_cubic-bezier(0.32,0.72,0,1),box-shadow_300ms_cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=right]:data-ending-style:shadow-[-2px_0_10px_rgb(0_0_0/0)] data-[swipe-direction=right]:data-swiping:duration-0 data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:mr-0 data-[swipe-direction=right]:supports-[-webkit-touch-callout:none]:pr-6',
                  // Right enter/exit
                  'data-[swipe-direction=right]:data-ending-style:transform-[translateX(calc(100%-var(--bleed)+var(--viewport-padding)))] data-[swipe-direction=right]:data-starting-style:transform-[translateX(calc(100%-var(--bleed)+var(--viewport-padding)))]',
                  // Left-only (with stacking transform + transition for box-shadow)
                  'data-[swipe-direction=left]:-ml-(--bleed) data-[swipe-direction=left]:origin-[var(--bleed)_50%] data-[swipe-direction=left]:transform-[translateX(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-x)+var(--stack-peek-offset)+(var(--shrink)*100%)))_scale(var(--scale))] data-[swipe-direction=left]:rounded-r-xl data-[swipe-direction=left]:pl-[calc(1.5rem+var(--bleed))] data-[swipe-direction=left]:shadow-[2px_0_10px_rgb(0_0_0/0.1)] data-[swipe-direction=left]:[transition:transform_300ms_cubic-bezier(0.32,0.72,0,1),box-shadow_300ms_cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=left]:data-ending-style:shadow-[2px_0_10px_rgb(0_0_0/0)] data-[swipe-direction=left]:data-swiping:duration-0 data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:ml-0 data-[swipe-direction=left]:supports-[-webkit-touch-callout:none]:pl-6',
                  // Left enter/exit
                  'data-[swipe-direction=left]:data-ending-style:transform-[translateX(calc(-100%+var(--bleed)-var(--viewport-padding)))] data-[swipe-direction=left]:data-starting-style:transform-[translateX(calc(-100%+var(--bleed)-var(--viewport-padding)))]',
                  // Shared vertical (up & down)
                  'data-[swipe-direction=down]:w-full data-[swipe-direction=up]:w-full',
                  'data-[swipe-direction=down]:max-h-[calc(80vh+var(--bleed))] data-[swipe-direction=up]:max-h-[calc(80vh+var(--bleed))]',
                  'data-[swipe-direction=down]:px-6 data-[swipe-direction=up]:px-6',
                  // Down-only (with stacking transform + transitions for height & box-shadow)
                  'data-[swipe-direction=down]:-mb-(--bleed) data-[swipe-direction=down]:h-(--drawer-height,auto) data-[swipe-direction=down]:origin-[50%_calc(100%-var(--bleed))] data-[swipe-direction=down]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--shrink)*var(--height))))_scale(var(--scale))] data-[swipe-direction=down]:rounded-t-xl data-[swipe-direction=down]:pt-4 data-[swipe-direction=down]:pb-[calc(1.5rem+env(safe-area-inset-bottom,0px)+var(--bleed))] data-[swipe-direction=down]:shadow-[0_2px_10px_rgb(0_0_0/0.1)] data-[swipe-direction=down]:[transition:transform_300ms_cubic-bezier(0.32,0.72,0,1),height_300ms_cubic-bezier(0.32,0.72,0,1),box-shadow_300ms_cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=down]:data-ending-style:shadow-[0_2px_10px_rgb(0_0_0/0)] data-[swipe-direction=down]:data-nested-drawer-open:h-[calc(var(--height)+var(--bleed))] data-[swipe-direction=down]:data-swiping:duration-0',
                  // Down enter/exit
                  'data-[swipe-direction=down]:data-ending-style:transform-[translateY(calc(100%-var(--bleed)))] data-[swipe-direction=down]:data-starting-style:transform-[translateY(calc(100%-var(--bleed)))]',
                  // Up-only (with stacking transform + transitions for height & box-shadow)
                  'data-[swipe-direction=up]:-mt-(--bleed) data-[swipe-direction=up]:h-(--drawer-height,auto) data-[swipe-direction=up]:origin-[50%_var(--bleed)] data-[swipe-direction=up]:transform-[translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)+var(--stack-peek-offset)+(var(--shrink)*var(--height))))_scale(var(--scale))] data-[swipe-direction=up]:rounded-b-xl data-[swipe-direction=up]:pt-[calc(1.5rem+env(safe-area-inset-top,0px)+var(--bleed))] data-[swipe-direction=up]:pb-6 data-[swipe-direction=up]:shadow-[0_-2px_10px_rgb(0_0_0/0.1)] data-[swipe-direction=up]:[transition:transform_300ms_cubic-bezier(0.32,0.72,0,1),height_300ms_cubic-bezier(0.32,0.72,0,1),box-shadow_300ms_cubic-bezier(0.32,0.72,0,1)] data-[swipe-direction=up]:data-ending-style:shadow-[0_-2px_10px_rgb(0_0_0/0)] data-[swipe-direction=up]:data-nested-drawer-open:h-[calc(var(--height)+var(--bleed))] data-[swipe-direction=up]:data-swiping:duration-0',
                  // Up enter/exit
                  'data-[swipe-direction=up]:data-ending-style:transform-[translateY(calc(-100%+var(--bleed)))] data-[swipe-direction=up]:data-starting-style:transform-[translateY(calc(-100%+var(--bleed)))]',
                  className
                )
          }
          {...props}
        >
          {!unstyled && showHandle !== false && (
            <DrawerHandle
              className={
                showHandle
                  ? undefined
                  : 'hidden group-data-[swipe-direction=down]/popup:block group-data-[swipe-direction=up]/popup:block'
              }
            />
          )}
          {children}
        </DrawerPrimitive.Popup>
      </DrawerViewport>
    </DrawerPortal>
  );
}

function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot='drawer-header'
      className={cn('flex flex-col gap-2', className)}
      {...props}
    />
  );
}

function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
  return (
    <div
      data-slot='drawer-footer'
      className={cn(
        'mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
        className
      )}
      {...props}
    />
  );
}

function DrawerTitle({
  className,
  ...props
}: Omit<DrawerPrimitive.Title.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Title
      data-slot='drawer-title'
      className={cn(
        'text-base font-medium text-foreground group-data-[swipe-direction=down]/popup:text-center group-data-[swipe-direction=up]/popup:text-center',
        className
      )}
      {...props}
    />
  );
}

function DrawerDescription({
  className,
  ...props
}: Omit<DrawerPrimitive.Description.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Description
      data-slot='drawer-description'
      className={cn(
        'mt-1.5 text-sm text-muted-foreground group-data-[swipe-direction=down]/popup:text-center group-data-[swipe-direction=up]/popup:text-center',
        className
      )}
      {...props}
    />
  );
}

function DrawerIndentBackground({
  className,
  ...props
}: DrawerPrimitive.IndentBackground.Props) {
  return (
    <DrawerPrimitive.IndentBackground
      data-slot='drawer-indent-background'
      className='absolute inset-0 isolate bg-foreground/70'
      {...props}
    />
  );
}

function DrawerIndent({
  className,
  ...props
}: Omit<DrawerPrimitive.Indent.Props, 'className'> & {
  className?: string;
}) {
  return (
    <DrawerPrimitive.Indent
      data-slot='drawer-indent'
      className={cn(
        'relative origin-[center_top] transform-[scale(1)_translateY(0)] border bg-background p-4 duration-[calc(400ms*var(--indent-transition)),calc(250ms*var(--indent-transition))] will-change-transform [--indent-progress:var(--drawer-swipe-progress)] [--indent-radius:calc(1rem*(1-var(--indent-progress)))] [--indent-transition:calc(1-clamp(0,calc(var(--drawer-swipe-progress)*100000),1))] [transition:transform_0.4s_cubic-bezier(0.32,0.72,0,1),border-radius_0.25s_cubic-bezier(0.32,0.72,0,1)] data-active:transform-[scale(calc(0.98+(0.02*var(--indent-progress))))_translateY(calc(0.5rem*(1-var(--indent-progress))))] data-active:rounded-tl-(--indent-radius) data-active:rounded-tr-(--indent-radius)',
        className
      )}
      {...props}
    />
  );
}

const createDrawerHandle = DrawerPrimitive.createHandle;

export {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHandle,
  DrawerHeader,
  DrawerIndent,
  DrawerIndentBackground,
  DrawerOverlay,
  DrawerPopup,
  DrawerPortal,
  DrawerProvider,
  DrawerTitle,
  DrawerTrigger,
  DrawerViewport,
  createDrawerHandle,
};
```

**Update the import paths to match your project setup.**

## Usage

```tsx
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
```

```tsx
<Drawer>
  <DrawerTrigger>Open</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Drawer Title</DrawerTitle>
      <DrawerDescription>Drawer description goes here.</DrawerDescription>
    </DrawerHeader>
    <DrawerFooter>
      <DrawerClose>Close</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>
```

## Examples

### Right Side

Use `swipeDirection="right"` on the `Drawer` component to slide the panel in from the right.

```tsx
'use client';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

export function DrawerRight() {
  return (
    <Drawer swipeDirection='right'>
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Drawer
      </DrawerTrigger>
      <DrawerPopup>
        <DrawerContent>
          <DrawerHeader>
            <DrawerTitle>Drawer</DrawerTitle>
            <DrawerDescription>
              This is a drawer that slides in from the side. You can swipe to
              dismiss it.
            </DrawerDescription>
          </DrawerHeader>
          <DrawerFooter>
            <DrawerClose render={<Button variant='outline' />}>
              Close
            </DrawerClose>
          </DrawerFooter>
        </DrawerContent>
      </DrawerPopup>
    </Drawer>
  );
}
```

### Non-Modal

Set `modal={false}` and `disablePointerDismissal` on the `Drawer` to keep the page interactive while the drawer is open.

```tsx
'use client';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

export function DrawerNonModal() {
  return (
    <Drawer swipeDirection='right' modal={false} disablePointerDismissal>
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Non-Modal Drawer
      </DrawerTrigger>
      <DrawerPopup>
        <DrawerContent>
          <DrawerHeader>
            <DrawerTitle>Non-modal Drawer</DrawerTitle>
            <DrawerDescription>
              This drawer does not trap focus and ignores outside clicks. Use
              the close button or swipe to dismiss it.
            </DrawerDescription>
          </DrawerHeader>
          <DrawerFooter>
            <DrawerClose render={<Button variant='outline' />}>
              Close
            </DrawerClose>
          </DrawerFooter>
        </DrawerContent>
      </DrawerPopup>
    </Drawer>
  );
}
```

### Nested Drawers

Nest multiple `Drawer` components and manage each with controlled `open` / `onOpenChange` state to create a stacking effect.

```tsx
'use client';

import * as React from 'react';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

export function DrawerNested() {
  const [firstOpen, setFirstOpen] = React.useState(false);
  const [secondOpen, setSecondOpen] = React.useState(false);
  const [thirdOpen, setThirdOpen] = React.useState(false);

  return (
    <Drawer
      open={firstOpen}
      onOpenChange={(nextOpen) => {
        setFirstOpen(nextOpen);
        if (!nextOpen) {
          setSecondOpen(false);
          setThirdOpen(false);
        }
      }}
    >
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Drawer Stack
      </DrawerTrigger>
      <DrawerPopup>
        <DrawerContent>
          <DrawerTitle className='mb-1'>Account</DrawerTitle>
          <DrawerDescription className='mb-6'>
            Nested drawers can be styled to stack, while each drawer remains
            independently focus managed.
          </DrawerDescription>

          <div className='flex items-center justify-end gap-4'>
            <div className='mr-auto'>
              <Drawer
                open={secondOpen}
                onOpenChange={(nextOpen) => {
                  setSecondOpen(nextOpen);
                  if (!nextOpen) {
                    setThirdOpen(false);
                  }
                }}
              >
                <DrawerTrigger
                  render={<Button variant='link' className='px-0' />}
                >
                  Security settings
                </DrawerTrigger>
                <DrawerPopup>
                  <DrawerContent>
                    <DrawerTitle className='mb-1'>Security</DrawerTitle>
                    <DrawerDescription className='mb-6'>
                      Review sign-in activity and update your security
                      preferences.
                    </DrawerDescription>

                    <ul className='mb-6 list-disc pl-5 text-muted-foreground'>
                      <li>Passkeys enabled</li>
                      <li>2FA via authenticator app</li>
                      <li>3 signed-in devices</li>
                    </ul>

                    <div className='flex items-center justify-end gap-4'>
                      <div className='mr-auto'>
                        <Drawer open={thirdOpen} onOpenChange={setThirdOpen}>
                          <DrawerTrigger
                            render={<Button variant='link' className='px-0' />}
                          >
                            Advanced options
                          </DrawerTrigger>
                          <DrawerPopup>
                            <DrawerContent>
                              <DrawerTitle className='mb-1'>
                                Advanced
                              </DrawerTitle>
                              <DrawerDescription className='mb-6'>
                                This drawer is taller to demonstrate
                                variable-height stacking.
                              </DrawerDescription>

                              <div className='mb-4 grid gap-1.5'>
                                <label
                                  className='text-sm font-medium'
                                  htmlFor='device-name'
                                >
                                  Device name
                                </label>
                                <input
                                  id='device-name'
                                  className='w-full rounded-md border border-input bg-background px-2.5 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none'
                                  defaultValue='Personal laptop'
                                />
                              </div>

                              <div className='mb-6 grid gap-1.5'>
                                <label
                                  className='text-sm font-medium'
                                  htmlFor='notes'
                                >
                                  Notes
                                </label>
                                <textarea
                                  id='notes'
                                  className='w-full resize-y rounded-md border border-input bg-background px-2.5 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none'
                                  defaultValue='Rotate recovery codes and revoke older sessions.'
                                  rows={3}
                                />
                              </div>

                              <div className='flex justify-end'>
                                <DrawerClose
                                  render={<Button variant='outline' />}
                                >
                                  Done
                                </DrawerClose>
                              </div>
                            </DrawerContent>
                          </DrawerPopup>
                        </Drawer>
                      </div>

                      <DrawerClose render={<Button variant='outline' />}>
                        Close
                      </DrawerClose>
                    </div>
                  </DrawerContent>
                </DrawerPopup>
              </Drawer>
            </div>

            <DrawerClose render={<Button variant='outline' />}>
              Close
            </DrawerClose>
          </div>
        </DrawerContent>
      </DrawerPopup>
    </Drawer>
  );
}
```

### Snap Points

Pass a `snapPoints` array to `Drawer` and use `unstyled` on `DrawerPopup` to apply custom popup styling for snap behavior.

```tsx
'use client';

import * as React from 'react';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

const TOP_MARGIN_REM = 1;
const VISIBLE_SNAP_POINTS_REM = [30];

function toViewportSnapPoint(heightRem: number) {
  return `${heightRem + TOP_MARGIN_REM}rem`;
}

const snapPoints = [...VISIBLE_SNAP_POINTS_REM.map(toViewportSnapPoint), 1];

export function DrawerSnapPoints() {
  return (
    <Drawer snapPoints={snapPoints}>
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Snap Drawer
      </DrawerTrigger>
      <DrawerPopup
        unstyled
        className='relative flex max-h-[calc(100dvh-var(--top-margin))] min-h-0 w-full transform-[translateY(calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] touch-none flex-col overflow-visible rounded-t-xl bg-background pb-[max(0px,calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] text-foreground shadow-[0_-16px_48px_rgb(0_0_0/0.12),0_6px_18px_rgb(0_0_0/0.06)] ring-1 ring-foreground/10 transition-[transform,box-shadow] duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] outline-none [--bleed:3rem] after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-(--bleed) after:bg-background after:content-[""] data-ending-style:transform-[translateY(100%)] data-ending-style:pb-0 data-ending-style:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*300ms)] data-starting-style:transform-[translateY(100%)] data-starting-style:pb-0 data-starting-style:shadow-[0_-16px_48px_rgb(0_0_0/0),0_6px_18px_rgb(0_0_0/0)] data-swiping:transition-none data-swiping:select-none'
        style={
          { '--top-margin': `${TOP_MARGIN_REM}rem` } as React.CSSProperties
        }
      >
        <div className='shrink-0 touch-none border-b border-border px-6 pt-3.5 pb-3'>
          <div className='mx-auto h-1 w-12 rounded-full bg-muted-foreground/30' />
          <DrawerTitle className='mt-2.5 cursor-default'>
            Snap points
          </DrawerTitle>
        </div>
        <DrawerContent className='min-h-0 flex-1 touch-auto overflow-y-auto overscroll-contain px-6 pt-4 pb-[calc(1.5rem+env(safe-area-inset-bottom,0px))]'>
          <div className='mx-auto w-full max-w-87.5'>
            <DrawerDescription className='mb-4'>
              Drag the sheet to snap between a compact peek and a near
              full-height view.
            </DrawerDescription>
            <div className='mb-6 grid gap-3' aria-hidden>
              {Array.from({ length: 20 }, (_, index) => (
                <div
                  key={index}
                  className='h-12 rounded-xl border border-border bg-muted'
                />
              ))}
            </div>
            <div className='flex items-center justify-end gap-4'>
              <DrawerClose render={<Button variant='outline' />}>
                Close
              </DrawerClose>
            </div>
          </div>
        </DrawerContent>
      </DrawerPopup>
    </Drawer>
  );
}
```

### Action Sheet

Use `DrawerPopup` with `unstyled` and `overlayClassName` to customize the popup and overlay styling for an action sheet layout.

```tsx
'use client';

import * as React from 'react';

import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

const ACTIONS = [
  'Unfollow',
  'Mute',
  'Add to Favourites',
  'Add to Close Friends',
  'Restrict',
];

export function DrawerActionSheet() {
  const [open, setOpen] = React.useState(false);

  return (
    <Drawer open={open} onOpenChange={setOpen}>
      <DrawerTrigger render={<Button variant='outline' />}>
        Open Action Sheet
      </DrawerTrigger>
      <DrawerPopup
        unstyled
        overlayClassName='bg-black/40'
        className='pointer-events-none relative flex w-full max-w-md transform-[translateY(var(--drawer-swipe-movement-y))] touch-auto flex-col gap-3 overflow-y-auto overscroll-contain bg-transparent px-4 pt-4 pb-[calc(1rem+env(safe-area-inset-bottom,0px))] text-foreground ring-0 transition-transform duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] outline-none data-ending-style:transform-[translateY(calc(100%+1rem))] data-ending-style:duration-[calc(var(--drawer-swipe-strength)*300ms)] data-starting-style:transform-[translateY(calc(100%+1rem))] data-swiping:transition-none data-swiping:select-none'
      >
        <DrawerContent className='pointer-events-auto overflow-hidden rounded-2xl bg-background ring-1 ring-foreground/10'>
          <DrawerTitle className='sr-only'>Profile actions</DrawerTitle>
          <DrawerDescription className='sr-only'>
            Choose an action for this user.
          </DrawerDescription>

          <ul
            className='m-0 list-none divide-y divide-foreground/10 p-0'
            aria-label='Profile actions'
          >
            {ACTIONS.map((action, index) => (
              <li key={action}>
                {index === 0 && (
                  <DrawerClose className='sr-only'>
                    Close action sheet
                  </DrawerClose>
                )}
                <button
                  type='button'
                  className='block w-full border-0 bg-transparent px-5 py-4 text-center text-sm select-none hover:bg-muted focus-visible:bg-muted focus-visible:outline-none'
                  onClick={() => setOpen(false)}
                >
                  {action}
                </button>
              </li>
            ))}
          </ul>
        </DrawerContent>
        <div className='pointer-events-auto overflow-hidden rounded-2xl bg-background ring-1 ring-foreground/10'>
          <button
            type='button'
            className='block w-full border-0 bg-transparent px-5 py-4 text-center text-sm text-destructive select-none hover:bg-muted focus-visible:bg-muted focus-visible:outline-none'
            onClick={() => setOpen(false)}
          >
            Block User
          </button>
        </div>
      </DrawerPopup>
    </Drawer>
  );
}
```
