Drawer
A panel that slides in from the edge of the screen.
right
left
import { XIcon } from 'lucide-react'
import { Button } from '~/components/ui/button'
import { Drawer } from '~/components/ui/drawer'
import { IconButton } from '~/components/ui/icon-button'
export const Demo = (props: Drawer.RootProps) => {
  return (
    <Drawer.Root {...props}>
      <Drawer.Trigger asChild>
        <Button>Open Drawer</Button>
      </Drawer.Trigger>
      <Drawer.Backdrop />
      <Drawer.Positioner>
        <Drawer.Content>
          <Drawer.Header>
            <Drawer.Title>Title</Drawer.Title>
            <Drawer.Description>Description</Drawer.Description>
            <Drawer.CloseTrigger asChild position="absolute" top="3" right="4">
              <IconButton variant="ghost">
                <XIcon />
              </IconButton>
            </Drawer.CloseTrigger>
          </Drawer.Header>
          <Drawer.Body>{/* Content */}</Drawer.Body>
          <Drawer.Footer gap="3">
            <Drawer.CloseTrigger asChild>
              <Button variant="outline">Cancel</Button>
            </Drawer.CloseTrigger>
            <Button>Primary</Button>
          </Drawer.Footer>
        </Drawer.Content>
      </Drawer.Positioner>
    </Drawer.Root>
  )
}
import { XIcon } from 'lucide-solid'
import { Button } from '~/components/ui/button'
import { Drawer } from '~/components/ui/drawer'
import { IconButton } from '~/components/ui/icon-button'
export const Demo = (props: Drawer.RootProps) => {
  return (
    <Drawer.Root {...props}>
      <Drawer.Trigger
        asChild={(triggerProps) => <Button {...triggerProps()}>Open Drawer</Button>}
      />
      <Drawer.Backdrop />
      <Drawer.Positioner>
        <Drawer.Content>
          <Drawer.Header>
            <Drawer.Title>Title</Drawer.Title>
            <Drawer.Description>Description</Drawer.Description>
            <Drawer.CloseTrigger
              asChild={(closeProps) => (
                <IconButton
                  {...closeProps()}
                  variant="ghost"
                  style={{ position: 'absolute', top: '3', right: '4' }}
                >
                  <XIcon />
                </IconButton>
              )}
            />
          </Drawer.Header>
          <Drawer.Body>{/* Content */}</Drawer.Body>
          <Drawer.Footer gap="3">
            <Drawer.CloseTrigger
              asChild={(closeProps) => (
                <Button {...closeProps()} variant="outline">
                  Cancel
                </Button>
              )}
            />
            <Button>Primary</Button>
          </Drawer.Footer>
        </Drawer.Content>
      </Drawer.Positioner>
    </Drawer.Root>
  )
}
Usage
import { Drawer } from '~/components/ui/drawer'Installation
npx @park-ui/cli components add drawer1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/drawer.tsx
'use client'
import type { Assign, PolymorphicProps } from '@ark-ui/react'
import { Dialog } from '@ark-ui/react/dialog'
import { ark } from '@ark-ui/react/factory'
import { type DrawerVariantProps, drawer } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withRootProvider, withContext } = createStyleContext(drawer)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withRootProvider<
  Assign<Dialog.RootProviderBaseProps, DrawerVariantProps>
>(Dialog.RootProvider)
export type RootProps = ComponentProps<typeof Root>
export const Root = withRootProvider<Assign<Dialog.RootProps, DrawerVariantProps>>(Dialog.Root)
export const Backdrop = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, Dialog.BackdropBaseProps>
>(Dialog.Backdrop, 'backdrop')
export const CloseTrigger = withContext<
  HTMLButtonElement,
  Assign<HTMLStyledProps<'button'>, Dialog.CloseTriggerBaseProps>
>(Dialog.CloseTrigger, 'closeTrigger')
export const Content = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, Dialog.ContentBaseProps>
>(Dialog.Content, 'content')
export const Description = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, Dialog.DescriptionBaseProps>
>(Dialog.Description, 'description')
export const Positioner = withContext<
  HTMLDivElement,
  Assign<HTMLStyledProps<'div'>, Dialog.PositionerBaseProps>
>(Dialog.Positioner, 'positioner')
export const Title = withContext<
  HTMLHeadingElement,
  Assign<HTMLStyledProps<'h2'>, Dialog.TitleBaseProps>
>(Dialog.Title, 'title')
export const Trigger = withContext<
  HTMLButtonElement,
  Assign<HTMLStyledProps<'button'>, Dialog.TriggerBaseProps>
>(Dialog.Trigger, 'trigger')
export const Header = withContext<HTMLDivElement, Assign<HTMLStyledProps<'div'>, PolymorphicProps>>(
  ark.div,
  'header',
)
export const Body = withContext<HTMLDivElement, Assign<HTMLStyledProps<'div'>, PolymorphicProps>>(
  ark.div,
  'body',
)
export const Footer = withContext<HTMLDivElement, Assign<HTMLStyledProps<'div'>, PolymorphicProps>>(
  ark.div,
  'footer',
)
export {
  DialogContext as Context,
  type DialogContextProps as ContextProps,
} from '@ark-ui/react/dialog'
import { type Assign, Dialog, type PolymorphicProps, ark } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type DrawerVariantProps, drawer } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withRootProvider, withContext } = createStyleContext(drawer)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withRootProvider<Assign<Dialog.RootProps, DrawerVariantProps>>(Dialog.RootProvider)
export type RootProps = ComponentProps<typeof Root>
export const Root = withRootProvider<Assign<Dialog.RootProps, DrawerVariantProps>>(Dialog.Root)
export type BackdropProps = ComponentProps<typeof Backdrop>
export const Backdrop = withContext<Assign<HTMLStyledProps<'div'>, Dialog.BackdropProps>>(
  Dialog.Backdrop,
  'backdrop',
)
export type CloseTriggerProps = ComponentProps<typeof CloseTrigger>
export const CloseTrigger = withContext<
  Assign<HTMLStyledProps<'button'>, Dialog.CloseTriggerProps>
>(Dialog.CloseTrigger, 'closeTrigger')
export type ContentProps = ComponentProps<typeof Content>
export const Content = withContext<Assign<HTMLStyledProps<'div'>, Dialog.ContentProps>>(
  Dialog.Content,
  'content',
)
export type DescriptionProps = ComponentProps<typeof Description>
export const Description = withContext<Assign<HTMLStyledProps<'div'>, Dialog.DescriptionProps>>(
  Dialog.Description,
  'description',
)
export type PositionerProps = ComponentProps<typeof Positioner>
export const Positioner = withContext<Assign<HTMLStyledProps<'div'>, Dialog.PositionerProps>>(
  Dialog.Positioner,
  'positioner',
)
export type TitleProps = ComponentProps<typeof Title>
export const Title = withContext<Assign<HTMLStyledProps<'h2'>, Dialog.TitleProps>>(
  Dialog.Title,
  'title',
)
export type TriggerProps = ComponentProps<typeof Trigger>
export const Trigger = withContext<Assign<HTMLStyledProps<'button'>, Dialog.TriggerProps>>(
  Dialog.Trigger,
  'trigger',
)
export const Header = withContext<Assign<HTMLStyledProps<'div'>, PolymorphicProps<'div'>>>(
  ark.div,
  'header',
)
export const Body = withContext<Assign<HTMLStyledProps<'div'>, PolymorphicProps<'div'>>>(
  ark.div,
  'body',
)
export const Footer = withContext<Assign<HTMLStyledProps<'div'>, PolymorphicProps<'div'>>>(
  ark.div,
  'footer',
)
export {
  DialogContext as Context,
  type DialogContextProps as ContextProps,
} from '@ark-ui/solid'
No snippet found2
Add Re-Export
To improve the developer experience, re-export the styled primitives in~/components/ui/drawer.tsx.
export * as Drawer from './styled/drawer'
export * as Drawer from './styled/drawer'
3
Integrate Recipe
If you're not using @park-ui/preset, add the following recipe to yourpanda.config.ts:
import { dialogAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
const anatomy = dialogAnatomy.extendWith('header', 'body', 'footer')
export const drawer = defineSlotRecipe({
  className: 'drawer',
  slots: [...anatomy.keys()],
  base: {
    backdrop: {
      backdropFilter: 'blur(4px)',
      background: {
        _light: 'white.a10',
        _dark: 'black.a10',
      },
      height: '100vh',
      left: '0',
      position: 'fixed',
      top: '0',
      width: '100vw',
      zIndex: 'overlay',
      _open: {
        animation: 'backdrop-in',
      },
      _closed: {
        animation: 'backdrop-out',
      },
    },
    positioner: {
      alignItems: 'center',
      display: 'flex',
      height: '100dvh',
      justifyContent: 'center',
      position: 'fixed',
      top: 0,
      width: { base: '100vw', sm: 'sm' },
      zIndex: 'modal',
    },
    content: {
      background: 'bg.default',
      boxShadow: 'lg',
      display: 'grid',
      divideY: '1px',
      gridTemplateColumns: '1fr',
      gridTemplateRows: 'auto 1fr auto',
      gridTemplateAreas: `
        'header'
        'body'
        'footer'
      `,
      height: 'full',
      width: 'full',
      _hidden: {
        display: 'none',
      },
    },
    header: {
      display: 'flex',
      flexDirection: 'column',
      gap: '1',
      gridArea: 'header',
      pt: { base: '4', md: '6' },
      pb: '4',
      px: { base: '4', md: '6' },
    },
    body: {
      display: 'flex',
      flexDirection: 'column',
      gridArea: 'body',
      overflow: 'auto',
      p: { base: '4', md: '6' },
    },
    footer: {
      display: 'flex',
      gridArea: 'footer',
      justifyContent: 'flex-end',
      py: '4',
      px: { base: '4', md: '6' },
    },
    title: {
      color: 'fg.default',
      fontWeight: 'semibold',
      textStyle: 'xl',
    },
    description: {
      color: 'fg.muted',
      textStyle: 'sm',
    },
  },
  defaultVariants: {
    variant: 'right',
  },
  variants: {
    variant: {
      left: {
        positioner: {
          left: 0,
        },
        content: {
          _open: {
            animation: 'drawer-in-left',
          },
          _closed: {
            animation: 'drawer-out-left',
          },
        },
      },
      right: {
        positioner: {
          right: 0,
        },
        content: {
          _open: {
            animation: 'drawer-in-right',
          },
          _closed: {
            animation: 'drawer-out-right',
          },
        },
      },
    },
  },
})