A floating side panel from the start or end edge, with a title, a scrolling body and a pinned footer.

A drawer is a panel that slides in from the start or end edge, full height and floating 16px inside the viewport. It holds a header with the title and a close button, a body, and a pinned footer. The body scrolls when it is too tall, and the header and footer stay.

  • title is required, because it names the drawer.
  • isTitleHidden keeps the title for assistive technology only, for content that names itself, such as a navigation list. The header shows just the close button.
  • With hasCloseButton={false} as well, there is no header, and the body starts at the top.
  • footer holds what should stay in view, such as the signed-in account or an action.
  • A scrolling body with nothing to focus, such as a page of notes, is a tab stop of its own, so the keyboard can scroll it.

Example

Follows the page theme and corners
import { Button, DialogTrigger, Drawer, Link, Text } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

const PAGES = ['Overview', 'Projects', 'Members', 'Billing', 'Settings'];

export default function DrawerNavigation(): ReactElement {
  return (
    <DialogTrigger>
      <Button variant="secondary">Browse the workspace</Button>
      <Drawer
        placement="start"
        title="Workspace"
        isTitleHidden
        footer={
          <div style={{ minWidth: 0 }}>
            <Text as="span" style={{ display: 'block' }}>
              Ada Lovelace
            </Text>
            <Text as="span" size="small" tone="muted" style={{ display: 'block' }}>
              ada@northwind.example
            </Text>
          </div>
        }
      >
        <nav aria-label="Workspace pages">
          <ul style={{ display: 'grid', gap: 12, margin: 0, padding: 0, listStyle: 'none' }}>
            {PAGES.map((page) => (
              <li key={page}>
                <Link href={`#${page.toLowerCase()}`}>{page}</Link>
              </li>
            ))}
          </ul>
        </nav>
      </Drawer>
    </DialogTrigger>
  );
}

From the end

placement="end" brings the panel from the other edge, for a filter form beside a list. Put its action in the footer to keep it in view. A button with slot="close" closes the drawer it is in.

Example

Follows the page theme and corners
import { Button, Checkbox, CheckboxGroup, DialogTrigger, Drawer, Switch } from '@snapshot-ui/react';
import type { ReactElement } from 'react';

export default function DrawerEnd(): ReactElement {
  return (
    <DialogTrigger>
      <Button variant="secondary">Filter the projects</Button>
      <Drawer
        placement="end"
        title="Filters"
        footer={
          <Button slot="close" style={{ width: '100%' }}>
            Show 12 projects
          </Button>
        }
      >
        <div style={{ display: 'grid', gap: 24 }}>
          <CheckboxGroup label="Status" defaultValue={['active']}>
            <Checkbox value="active">Active</Checkbox>
            <Checkbox value="paused">Paused</Checkbox>
            <Checkbox value="archived">Archived</Checkbox>
          </CheckboxGroup>
          <Switch defaultSelected>Only projects I own</Switch>
        </div>
      </Drawer>
    </DialogTrigger>
  );
}

Usage

Open a drawer from a DialogTrigger, which holds the opening button and the drawer. Or control it with isOpen and onOpenChange, or start it open with defaultOpen. children can be a function that receives close, for a link that closes the drawer as it navigates. Drawer is a client component, and a server component can render it, as the examples here do.

The drawer closes only when asked. An app shell that shows a rail on wide screens and a drawer on narrow ones controls it with isOpen, and closes it when the viewport widens past its breakpoint.

  • Size: 20rem wide at most, and never wider than the screen less 2rem. It floats 16px from every edge, like the toast and a rail, with the dramatic modal shadow.
  • Direction: start and end follow the writing direction. In right to left, the start is the right edge.
  • Theme: a drawer opened from a dark card is dark while the page stays light, as Theming explains.

Props

ref goes on the dialog element, the one that takes focus. Unlike Dialog and Sheet, a drawer passes no other React Aria dialog props through, such as aria-describedby, id or aria-label. Its title names it, and anything else goes in its body. className goes on the panel and overlayClassName on the scrim, each merged after the library's classes. A class on the panel may restyle .drawer-body and .drawer-footer, such as to set your own padding. The header and other inner parts may change between releases.

children
((opts: { close: () => void; }) => ReactNode) | ReactNode

The body. It scrolls.

className
string | ((values: ModalRenderProps & { defaultClassName: string | undefined; }) => string)

Goes on the panel, merged after the library's classes.

closeLabel
string

The close button's accessible name.

Default: 'Close'

defaultOpen
boolean

footer
ReactNode

Pinned under the body, for an account row or an action.

hasCloseButton
boolean

An X in the header.

Default: true

isDismissable
boolean

A press on the scrim closes the drawer.

Default: true

isKeyboardDismissDisabled
boolean

Escape no longer closes it.

Default: false

isOpen
boolean

isTitleHidden
boolean

Keeps the title for assistive technology only, for a drawer whose content names itself, such as a navigation list.

Default: false

onOpenChange
((isOpen: boolean) => void)

overlayClassName
string

Goes on the scrim.

placement
DrawerPlacement

Which edge the panel comes from. start and end follow the writing direction.

Default: 'start'

title
ReactNode

The title, an h2 in the header. It names the drawer. Required.

Accessibility

The drawer is a dialog named by its title, even a hidden one. When it opens, focus moves in, and Tab cycles inside it without reaching the page. When it closes, focus returns to the button that opened it. While it is open, the page behind is hidden from assistive technology and does not scroll.

TabCycles through the controls inside.
EscapeCloses the drawer.
EnterSpacePress a button or follow a link.

Design rules

What keeps drawers in the system.

Use a drawer for navigation or for a filter form beside a list.

Hide the title when the content names itself, such as a navigation list, and keep it otherwise.

Keep the close button, so the way out is always in view.

Never use a drawer to ask a question. That is a confirm dialog.

Never widen a drawer past 20rem. Content that needs more room is a page or a dialog.

Never open two drawers at once.

esc

↑ ↓ to move↵ to open