Drawer
Slide a panel in from any edge of the screen for filters, details, and sheets, without leaving the page.
Installation
You can add the drawer using our CLI or manually:
Dark mode ready - this component follows BoardUI semantic tokens automatically. Set up theme switching.
The CLI copies the component source (with its internal dependencies) into your project and installs any required npm packages.
npx boardui@latest add drawerUsing a shared UI package or custom import aliases? Configure your installation paths before running the CLI. Copy-paste examples use the default imports.
Anatomy
Drawer is a sibling of Modal: the same trigger, header, scrolling body, and footer, re-exported as DrawerTrigger, DrawerHeader, DrawerBody, and DrawerFooter. The panel floats inside the edge of the screen with the same surface, and slides in while it sharpens from a soft blur.
Bottom sheet
Set placement="bottom" for a sheet that rises from the bottom, the natural home for actions on a phone. It grows with its content up to 85% of the screen, then its body scrolls.
<DrawerTrigger> <Button variant="secondary" leadingIcon={RiShareForwardLine}>Share report</Button> <Drawer placement="bottom"> {({ close }) => ( <> <DrawerHeader title="Share Q3 report" description="Anyone in the Acme workspace with the link can view it." /> <DrawerBody> <div className="grid grid-cols-2 gap-2 sm:grid-cols-4"> {targets.map((target) => ( <button key={target.label} type="button" onClick={close}>…</button> ))} </div> </DrawerBody> </> )} </Drawer></DrawerTrigger>Placement
placement takes start, end, top, or bottom. Start and end follow the reading direction, so an end drawer opens from the left in Arabic and Hebrew.
const [placement, setPlacement] = useState<DrawerPlacement | null>(null);
<Button variant="secondary" onClick={() => setPlacement("start")}>Start</Button><Button variant="secondary" onClick={() => setPlacement("end")}>End</Button><Button variant="secondary" onClick={() => setPlacement("top")}>Top</Button><Button variant="secondary" onClick={() => setPlacement("bottom")}>Bottom</Button>
<Drawer placement={placement ?? "end"} isOpen={placement !== null} onOpenChange={(open) => !open && setPlacement(null)}> <DrawerHeader title="Drawer" description="Start and end follow the reading direction. Top and bottom span the screen." /></Drawer>Long content
Side drawers take the full height of the screen, and DrawerBody scrolls on its own with the header held in place. A progressive blur softens the top edge once the list moves under it.
<DrawerTrigger> <Button variant="secondary" leadingIcon={RiHistoryLine}>View activity</Button> <Drawer size="sm"> <DrawerHeader title="Activity" description="Everything that happened in the Acme workspace." /> <DrawerBody> <ol className="flex flex-col gap-4"> {events.map((event) => ( <li key={event.id} className="flex items-start gap-3">…</li> ))} </ol> </DrawerBody> </Drawer></DrawerTrigger>Sizes
Side drawers come in three widths, all capped by the screen. Control the open state with isOpen and onOpenChange, as with Modal.
<Drawer size="sm">…</Drawer> // 320px<Drawer size="md">…</Drawer> // 400px, the default<Drawer size="lg">…</Drawer> // 600px
// Top and bottom drawers span the screen and grow to 85% of its height<Drawer placement="bottom">…</Drawer>Right-to-left
Wrap the region in DirectionProvider. The drawer carries the direction into its portal, so it slides in from the left and its contents mirror.
import { DirectionProvider } from "@/components/foundations/direction/direction";
<DirectionProvider locale="ar"> <DrawerTrigger> <Button variant="secondary">عوامل التصفية</Button> <Drawer> <DrawerHeader title="عوامل التصفية" description="تضييق قائمة العملاء." closeLabel="إغلاق" /> <DrawerBody>…</DrawerBody> </Drawer> </DrawerTrigger></DirectionProvider>Keyboard and accessibility
Opening the drawer moves focus inside it, Tab and Shift+Tab cycle through its controls, and Escape or a press on the backdrop closes it. Focus then returns to the trigger, and the page behind stops scrolling while the drawer is open.
Reduced motion replaces the slide and blur with a fade.