kosori

Drawer

A panel that slides in from the edge of the screen with swipe gestures, snap points, and nested drawer support.

Drawer wraps the Base UI Drawer primitive, composes Button and its scrollable panel through Base UI's render prop, and marks each part with data-slot. It is a client component: it supports swipe gestures, snap points and nested drawers, uses a portal, manages focus, and animates with tw-animate-css.

Installation

npx shadcn@latest add @kosori/drawer

Example

drawer-example.tsx
drawer-example
import { Button } from '@/components/ui/button';
import {
  Drawer,
  DrawerClose,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerPanel,
  DrawerPopup,
  DrawerTitle,
  DrawerTrigger,
} from '@/components/ui/drawer';

const DrawerExample = () => (
  <Drawer>
    <DrawerTrigger render={<Button variant='outline' />}>
      Nested drawers
    </DrawerTrigger>
    <DrawerPopup>
      <DrawerHeader>
        <DrawerTitle>First step</DrawerTitle>
        <DrawerDescription>
          This is the first step. Tap the button below to continue to the next
          screen.
        </DrawerDescription>
      </DrawerHeader>
      <DrawerFooter>
        <DrawerClose render={<Button variant='ghost' />}>Cancel</DrawerClose>
        <Drawer>
          <DrawerTrigger render={<Button variant='outline' />}>
            Continue
          </DrawerTrigger>
          <DrawerPopup>
            <DrawerHeader>
              <DrawerTitle>Second step</DrawerTitle>
              <DrawerDescription>
                You&apos;ve reached the second step. Tap the button below to
                continue to the next screen.
              </DrawerDescription>
            </DrawerHeader>
            <DrawerPanel>
              <div className='flex justify-center'>
                <div className='size-48 shrink-0 rounded-xl border bg-muted' />
              </div>
            </DrawerPanel>
            <DrawerFooter>
              <DrawerClose render={<Button variant='ghost' />}>
                Back
              </DrawerClose>
              <Drawer>
                <DrawerTrigger render={<Button variant='outline' />}>
                  Continue
                </DrawerTrigger>
                <DrawerPopup>
                  <DrawerHeader>
                    <DrawerTitle>Third step</DrawerTitle>
                    <DrawerDescription>
                      You&apos;ve reached the final step. You can close this
                      drawer or go back.
                    </DrawerDescription>
                  </DrawerHeader>
                  <DrawerPanel>
                    <div className='flex justify-center'>
                      <div className='size-32 shrink-0 rounded-full border bg-muted' />
                    </div>
                  </DrawerPanel>
                  <DrawerFooter>
                    <DrawerClose render={<Button variant='ghost' />}>
                      Close
                    </DrawerClose>
                  </DrawerFooter>
                </DrawerPopup>
              </Drawer>
            </DrawerFooter>
          </DrawerPopup>
        </Drawer>
      </DrawerFooter>
    </DrawerPopup>
  </Drawer>
);

export default DrawerExample;

Usage

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

const DrawerDemo = () => (
  <Drawer>
    <DrawerTrigger render={<Button variant='outline' />}>
      Open drawer
    </DrawerTrigger>
    <DrawerPopup>
      <DrawerHeader>
        <DrawerTitle>Edit profile</DrawerTitle>
        <DrawerDescription>Make changes to your profile.</DrawerDescription>
      </DrawerHeader>
      <DrawerFooter>
        <DrawerClose render={<Button variant='outline' />}>Cancel</DrawerClose>
        <Button>Save changes</Button>
      </DrawerFooter>
    </DrawerPopup>
  </Drawer>
);

On this page