Skip to main content

Overview

Drawer is a slide-in panel component that appears from any edge of the screen. It includes swipe-to-dismiss functionality, backdrop overlay, and follows the same composable pattern as Dialog.

Basic usage

import { Drawer } from '@raystack/apsara';

<Drawer>
  <Drawer.Trigger asChild>
    <button>Open drawer</button>
  </Drawer.Trigger>
  
  <Drawer.Content>
    <Drawer.Header>
      <Drawer.Title>Drawer title</Drawer.Title>
      <Drawer.Description>Optional description</Drawer.Description>
    </Drawer.Header>
    
    <Drawer.Body>
      Drawer content goes here.
    </Drawer.Body>
    
    <Drawer.Footer>
      <Drawer.Close asChild>
        <button>Close</button>
      </Drawer.Close>
    </Drawer.Footer>
  </Drawer.Content>
</Drawer>

Components

Drawer (Root)

The root component that manages drawer state and swipe behavior.
side
'top' | 'right' | 'bottom' | 'left'
default:"'right'"
The direction from which the drawer appears and can be swiped to dismiss.
open
boolean
Controls the open state of the drawer (controlled mode).
defaultOpen
boolean
The initial open state in uncontrolled mode.
onOpenChange
(open: boolean) => void
Callback fired when the open state changes.
swipeDirection
'top' | 'right' | 'bottom' | 'left'
Override the swipe direction. By default, matches the side prop.

Drawer.Trigger

A button that opens the drawer when clicked.
asChild
boolean
When true, merges props with the immediate child element instead of rendering a button.

Drawer.Content

The drawer panel rendered in a portal with backdrop and viewport.
side
'top' | 'right' | 'bottom' | 'left'
default:"'right'"
The edge from which the drawer slides in.
showCloseButton
boolean
default:"true"
Whether to show the close button in the top-right corner.
overlayProps
object
Props to pass to the backdrop overlay component.
className
string
Additional CSS classes for the drawer popup.

Drawer.Header

A container for the drawer header content.

Drawer.Title

The accessible title element for the drawer. Required for accessibility.

Drawer.Description

An optional description element that provides additional context.

Drawer.Body

A container for the main drawer content. A container for action buttons at the bottom of the drawer.

Drawer.Close

A button that closes the drawer when clicked.
asChild
boolean
When true, merges props with the immediate child element instead of rendering a button.

Usage examples

Drawer from left

<Drawer side="left">
  <Drawer.Trigger asChild>
    <button>Open left drawer</button>
  </Drawer.Trigger>
  
  <Drawer.Content side="left">
    <Drawer.Header>
      <Drawer.Title>Left drawer</Drawer.Title>
    </Drawer.Header>
    <Drawer.Body>
      This drawer slides in from the left.
    </Drawer.Body>
  </Drawer.Content>
</Drawer>

Bottom drawer

<Drawer side="bottom">
  <Drawer.Trigger asChild>
    <button>Open bottom drawer</button>
  </Drawer.Trigger>
  
  <Drawer.Content side="bottom">
    <Drawer.Header>
      <Drawer.Title>Bottom drawer</Drawer.Title>
    </Drawer.Header>
    <Drawer.Body>
      This drawer slides up from the bottom. Swipe down to dismiss.
    </Drawer.Body>
  </Drawer.Content>
</Drawer>

Without close button

<Drawer>
  <Drawer.Trigger asChild>
    <button>Open drawer</button>
  </Drawer.Trigger>
  
  <Drawer.Content showCloseButton={false}>
    <Drawer.Header>
      <Drawer.Title>No close button</Drawer.Title>
    </Drawer.Header>
    <Drawer.Body>
      Use swipe gesture or footer button to close.
    </Drawer.Body>
    <Drawer.Footer>
      <Drawer.Close asChild>
        <button>Close</button>
      </Drawer.Close>
    </Drawer.Footer>
  </Drawer.Content>
</Drawer>

Controlled drawer

function ControlledDrawer() {
  const [open, setOpen] = useState(false);
  
  return (
    <Drawer open={open} onOpenChange={setOpen}>
      <Drawer.Trigger asChild>
        <button>Open drawer</button>
      </Drawer.Trigger>
      
      <Drawer.Content>
        <Drawer.Header>
          <Drawer.Title>Controlled drawer</Drawer.Title>
        </Drawer.Header>
        <Drawer.Body>
          This drawer's state is controlled externally.
        </Drawer.Body>
        <Drawer.Footer>
          <button onClick={() => setOpen(false)}>Close</button>
        </Drawer.Footer>
      </Drawer.Content>
    </Drawer>
  );
}

Swipe-to-dismiss

The drawer automatically enables swipe-to-dismiss functionality based on the side prop:
  • Right drawer: Swipe right to dismiss
  • Left drawer: Swipe left to dismiss
  • Top drawer: Swipe up to dismiss
  • Bottom drawer: Swipe down to dismiss
You can override the swipe direction using the swipeDirection prop on the root component:
<Drawer side="right" swipeDirection="left">
  {/* Drawer content */}
</Drawer>

Accessibility features

  • Focus trap: Focus is trapped within the drawer when open
  • ESC to close: Press Escape to close the drawer
  • Focus return: Focus returns to the trigger element when closed
  • ARIA labels: Proper ARIA attributes including aria-label="Drawer"
  • Close button: Includes accessible aria-label="Close Drawer"
  • Swipe gestures: Native swipe-to-dismiss for touch devices

Trigger patterns

The Drawer.Trigger component can be used in two ways:
  1. Default button: Renders a button element when no children are provided or asChild is false
  2. Custom trigger: Use asChild prop to merge drawer trigger functionality with your own button component
{/* Default button */}
<Drawer.Trigger>Open</Drawer.Trigger>

{/* Custom trigger */}
<Drawer.Trigger asChild>
  <button className="custom-button">Open</button>
</Drawer.Trigger>