Drawer

Slide a side panel in from the left or right, toggled by a matching DrawerButton.

Drawer is a client component that renders a full-height side panel with a header, close button, and backdrop, portalled to document.body. Use it for navigation, filters, or settings that should not take space in the page layout. Drawer and DrawerButton share open state by id through useSidebar, which requires a SidebarProvider ancestor. DesignSystemProvider mounts one for you (see Notes).

Open navigation

Import#

typescript
import { Drawer, DrawerButton } from "@reactberry/system/blocks";

Usage#

typescript
import { Drawer, DrawerButton } from "@reactberry/system/blocks";
import { Box, Text } from "@reactberry/system/elements";

export default function FiltersPanel() {
  return (
    <>
      <DrawerButton drawerId="filters" label="Filters" />
      <Drawer id="filters" title="Filters" placement="right" width={{ _: "100%", md: "400px" }}>
        <Box p="medium">
          <Text>Filter controls</Text>
        </Box>
      </Drawer>
    </>
  );
}

Examples#

Right placement#

Set placement="right" and pass a responsive width. icon replaces the default plus icon on the trigger.

Display settings

Custom trigger content#

Children of DrawerButton replace its icon and label.

Cart (2)

API#

Drawer#

PropTypeDefaultDescription
children*
ReactNode
—

Content rendered below the header.

title
string
Drawer

Header text.

id*
string
—

State key shared with DrawerButton.

width
string | number | object
350px

Panel width. Responsive objects are passed through to Box.

placement
"left""right"
"left"

Side the panel is attached to and slides in from.

children (required) is rendered below the header. No other props are accepted.

DrawerButton#

PropTypeDefaultDescription
drawerId*
string
—

Id of the Drawer to toggle.

label
string
Open

Button text, hidden below the md breakpoint.

icon
ReactNode
<Box as={IconEAdd} size=…

Icon rendered before the label.

children
ReactNode
—

Rendered instead of icon and label when set.

Any other props are spread onto the Button after its defaults ($size="xxsmall", variant of "primary" when open and "ghost" when closed). When children are passed, they are rendered instead of icon and label. For an icon-only trigger, pass label="" and an aria-label.

Accessibility#

  • The header close button has aria-label="Close drawer".
  • The panel has no dialog role, no aria-modal, and focus is neither moved into it nor trapped.
  • DrawerButton hides its label below the md breakpoint with display: none, leaving only the icon; add an aria-label when the icon alone does not describe the action.

Notes#

  • useSidebar reads state from SidebarProvider. DesignSystemProvider mounts SidebarProvider around its children, and both SidebarProvider and useSidebar are exported from @reactberry/system/providers. Outside a provider, the drawer stays closed, DrawerButton does nothing, and a useSidebar must be used within a SidebarProvider warning is logged.
  • Open state is stored in localStorage under panel_ followed by the id, so it persists across reloads.
  • Clicking the backdrop or the header close button toggles the drawer closed. Escape is not handled.
  • While open, the drawer locks body scroll and, on touch devices, tints the browser theme colour via pushThemeColor. It also renders an overscroll guard to cover the bottom safe area on iOS.
  • The panel slides with a spring transition and uses skin="panel" and zIndex 10009; the backdrop uses 10002.
  • Nothing is rendered until after the first client mount.