Skip to content

Drawer component

Drawer is a slide-in panel from any edge of the viewport — filters, notifications, a details view opened from a table row. It's built on the same overlay stack, focus-trapping, and backdrop machinery as Modal, so the two behave consistently and stack correctly together; the difference is purely presentational (an edge-anchored panel instead of a centered box).

Navigation27 propsAccessibility notes

Playground

Change a prop on the right and the preview updates. Switch to Code to copy what you see.

Recipes

Copy-paste patterns for the ways Drawer actually gets used — each one a real, self-contained example.

Slide-over panel

Same controlled shape as Modal — you own isOpen, and onClose fires from every dismissal path. Slides in from the right by default.

const [isOpen, setIsOpen] = useState(false);

<Button variant="ghost" onPress={() => setIsOpen(true)}>Notifications</Button>
<Drawer
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Notifications"
  position="right"
>
  <NotificationsList />
</Drawer>

Any edge

position picks the edge it slides from — left, right, top, or bottom. width sizes left/right drawers; height sizes top/bottom ones.

<Drawer isOpen={isOpen} onClose={onClose} position="left" width="320px" title="Filters">
  <FilterForm />
</Drawer>

<Drawer isOpen={isOpen} onClose={onClose} position="bottom" height="60%" title="Details">
  <DetailsPanel />
</Drawer>

Header, body, footer

The same three slots as Modal — title, children, and an actions footer for the confirm/cancel buttons.

<Drawer
  isOpen={isOpen}
  onClose={onClose}
  title="Edit item"
  actions={
    <>
      <Button variant="soft" onPress={onClose}>Cancel</Button>
      <Button color="primary" onPress={handleSave}>Save</Button>
    </>
  }
>
  <EditForm />
</Drawer>

Basic controlled drawer

Same controlled shape as Modal: you own isOpen, onClose fires from every dismissal path (backdrop click, Escape, the header close button).

const NotificationsDrawer = () => {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button variant="ghost" onPress={() => setIsOpen(true)}>Notifications</Button>
      <Drawer isOpen={isOpen} onClose={() => setIsOpen(false)} title="Notifications" position="right">
        <NotificationsList />
      </Drawer>
    </>
  );
};

Position and sizing

position picks the edge it slides in from (left, right, top, bottom — right is the default). width applies to left/right drawers, height to top/bottom ones; both accept the usual token scale or a raw string/number for an exact size.

<Drawer isOpen={isOpen} onClose={onClose} position="left" width="320px" title="Filters">
  <FilterForm />
</Drawer>

<Drawer isOpen={isOpen} onClose={onClose} position="bottom" height="60%" title="Details">
  <DetailsPanel />
</Drawer>

Same three-slot shape as Modal — title for the header, children for the body, actions for the footer. Any of the three can be omitted independently.

<Drawer
  isOpen={isOpen}
  onClose={onClose}
  title="Edit item"
  actions={
    <>
      <Button variant="soft" onPress={onClose}>Cancel</Button>
      <Button color="primary" onPress={handleSave}>Save</Button>
    </>
  }
>
  <ItemForm />
</Drawer>

Stacking with Modal

Because Drawer shares Modal's overlay stack, opening a Drawer from inside an already-open Modal (or vice versa) layers correctly without any manual z-index coordination, and Escape only dismisses the topmost overlay.

Props

The full public API for Drawer. Every prop is stable and intentionally typed.

Content

title

ReactNode

default: —

Header title.

children

ReactNode

default: —

Body content.

actions

ReactNode

default: —

Footer action buttons.

Appearance

blur

boolean | "sm" | "md" | "lg" | number

default: —

Backdrop blur amount.

backgroundOpacity

number

default: —

Backdrop opacity (0-100).

Layout & positioning

position

"left" | "right" | "top" | "bottom"

default: right

Edge the drawer slides in from.

width

"xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number

default: "420px"

Panel width, for left/right positions.

height

"xs" | "sm" | "md" | "lg" | "xl" | "full" | string | number

default: "360px"

Panel height, for top/bottom positions.

portal

boolean

default: true

Renders through a React portal.

container

Element | DocumentFragment | null

default: —

Custom portal mount target.

zIndex

number

default: —

Explicit stacking z-index; auto-computed when unset.

Behavior & states

isOpen

boolean

default: —

Controls whether the drawer is open (required).

ariaLabel

string

default: "Drawer"

Accessible name for the drawer region.

dismissible

boolean

default: true

Shows a close button in the header.

closeLabel

string

default: "Close drawer"

Accessible label for the close button.

closeOnOutsideClick

boolean

default: true

Closes on backdrop click.

closeOnEscape

boolean

default: true

Closes on Escape.

initialFocusRef

RefObject<HTMLElement | null>

default: —

Element to focus when the drawer opens.

Motion

animationDuration

number

default: —

Enter/exit animation duration in seconds.

easing

"ease" | "easeIn" | "easeOut" | "easeInOut"

default: —

Animation easing curve.

Events

onClose

() => void

default: —

Called to close the drawer (required).

Styling

backdrop

Partial<BackdropProps>

default: —

Extra props forwarded to the underlying Backdrop.

headers

Partial<PageTitleProps>

default: —

Extra props forwarded to the header's PageTitle.

divider

Partial<DividerProps>

default: —

Extra props for the header/body divider.

classNames

Partial<Record<DrawerSlot, string>>

default: —

Per-slot class overrides (base/header/headerTitle/body/footer/closeButton).

className

string

default: —

Class applied to the root drawer element.

style

CSSProperties

default: —

Inline styles for the root element.

Accessibility

  • Traps focus inside the drawer while open and restores it to the triggering element on close, same as Modal.
  • ariaLabel (defaults to "Drawer") names the drawer region for assistive technology; set it to something specific when title alone doesn't describe the drawer's purpose clearly enough on its own.
  • closeOnEscape (on by default) closes the drawer on Escape, respecting the shared overlay stack when multiple overlays are open at once.
  • initialFocusRef sends initial focus somewhere specific on open (a search input at the top of a filters drawer, for instance) instead of the drawer's default first-focusable-element behavior.

Best practices

  • Use position="right" (the default) for supplementary content read alongside the page (details, filters) and position="bottom" for content that reads more like an action sheet, especially on narrower viewports.
  • Keep a Drawer's width/height proportionate to its content — an oversized drawer for a two-field form just wastes space and pushes the actual content further from the edge it slid in from.
  • For content that should block the page entirely and demand a decision, prefer Modal — Drawer's edge-anchored layout reads as supplementary, not as the primary focus of the screen.
  • Set closeOnOutsideClick={false} for a drawer holding an in-progress, hard-to-recreate form (a multi-field filter set) so an accidental outside click doesn't discard it.