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).
Import#
import { Drawer, DrawerButton } from "@reactberry/system/blocks";
Usage#
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.
Custom trigger content#
Children of DrawerButton replace its icon and label.
API#
Drawer#
| Prop | Type | Default | Description |
|---|---|---|---|
children* | ReactNode | — | Content rendered below the header. |
title | string | Drawer | Header text. |
id* | string | — | State key shared with |
width | string | number | object | 350px | Panel width. Responsive objects are passed through to |
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#
| Prop | Type | Default | Description |
|---|---|---|---|
drawerId* | string | — | Id of the |
label | string | Open | Button text, hidden below the |
icon | ReactNode | <Box as={IconEAdd} size=… | Icon rendered before the label. |
children | ReactNode | — | Rendered instead of |
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
dialogrole, noaria-modal, and focus is neither moved into it nor trapped. DrawerButtonhides its label below themdbreakpoint withdisplay: none, leaving only the icon; add anaria-labelwhen the icon alone does not describe the action.
Notes#
useSidebarreads state fromSidebarProvider.DesignSystemProvidermountsSidebarProvideraround its children, and bothSidebarProvideranduseSidebarare exported from@reactberry/system/providers. Outside a provider, the drawer stays closed,DrawerButtondoes nothing, and auseSidebar must be used within a SidebarProviderwarning is logged.- Open state is stored in
localStorageunderpanel_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"andzIndex10009; the backdrop uses10002. - Nothing is rendered until after the first client mount.