6.8 KiB
Drawer
(no submodules)
- drawer.variants — `drawerOverlayVariants`, `drawerC…-
drawer.variants —
drawerOverlayVariants,drawerContentVariants,drawerHandleVariants,drawerHeaderVariants, `drawerFooterVariants… -
drawer.variants —
drawerOverlayVariants, `drawerC… -
drawer.variants —
drawerOverlayVariants,drawerContentVariants,drawerHandleVariants,drawerHeaderVariants, `drawerFooterVariants… -
drawer.variants —
drawerOverlayVariants, `drawerC… -
drawer.variants —
drawerOverlayVariants,drawerContentVariants,drawerHandleVariants,drawerHeaderVariants, `drawerFooterVariants… -
drawer.variants —
drawerOverlayVariants, `drawerC… -
drawer.variants —
drawerOverlayVariants,drawerContentVariants,drawerHandleVariants,drawerHeaderVariants, `drawerFooterVariants… -
drawer.variants —
drawerOverlayVariants,drawerContentVariants,drawerHandleVariants,drawerHeaderVariants, `drawerFooterVariants…
Purpose
<Drawer> and its sub-components (<DrawerTrigger>, <DrawerPortal>, <DrawerOverlay>, <DrawerContent>, <DrawerHandle>, <DrawerHeader>, <DrawerFooter>, <DrawerTitle>, <DrawerDescription>, <DrawerClose>) are the chassis bottom-anchored modal panel primitives built on vaul. Vaul wraps @radix-ui/react-dialog internally so the WAI-ARIA dialog semantics (focus-trap, restore-focus, Escape closes, click-outside closes) are inherited; vaul layers on the swipe-down dismiss gesture, snap-points, and background-scale animation that distinguish a Drawer from a Sheet.
Per the WAI-ARIA dialog pattern, every drawer MUST contain a <DrawerTitle> so the accessible name is announced. Use className="sr-only" if the visible UI does not need a heading. Drawer is mobile-first — the panel slides up from the bottom and is grabbable by the small handle at the top.
Features
- Bottom-anchored slide-up panel with swipe-down dismiss gesture (vaul-native).
- Snap-points support via vaul (
snapPoints={[0.4, 0.8]}). - Background-scale animation while the drawer is open (
shouldScaleBackground, defaulttrue). - Drag handle at the top of the panel (rendered automatically inside
<DrawerContent>). - Focus-trap on open; restore-focus on close (vaul → Radix-managed).
- Escape closes the drawer; click on the overlay backdrop closes the drawer.
- All sub-components forward refs.
- Theme-driven cva overrides for every slot via
useThemedVariants("Drawer<Slot>").
Usage: Basic Example
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@suaveplan/react-ui";
export function AccountDrawer() {
return (
<Drawer>
<DrawerTrigger>Open settings</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Account settings</DrawerTitle>
<DrawerDescription>
Manage your account preferences.
</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose>Close</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
Usage: Advanced Example
import {
Button,
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
} from "@suaveplan/react-ui";
import { useState } from "react";
export function SnappingFiltersDrawer() {
const [open, setOpen] = useState(false);
return (
<Drawer
open={open}
onOpenChange={setOpen}
snapPoints={[0.4, 0.7, 1.0]}
shouldScaleBackground
>
<Button onClick={() => setOpen(true)}>Open filters</Button>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Filters</DrawerTitle>
<DrawerDescription>
Drag the handle to expand.
</DrawerDescription>
</DrawerHeader>
<div className="p-4">…filter controls…</div>
<DrawerFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => setOpen(false)}>Apply</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
}
API Reference
<Drawer>
Wraps vaul's Drawer.Root. Inherits every prop from vaul including:
| Prop | Type | Default | Description |
|---|---|---|---|
open |
boolean |
uncontrolled | Controlled open state. |
defaultOpen |
boolean |
false |
Initial open state. |
onOpenChange |
(open: boolean) => void |
none | Fires on open/close transitions. |
snapPoints |
(number | string)[] |
none | Optional snap-point heights. |
shouldScaleBackground |
boolean |
true |
Scale the page behind the drawer while open. |
dismissible |
boolean |
true |
Allow Escape / click-outside / drag-down dismissal. |
modal |
boolean |
true |
Keep focus trapped inside the drawer. |
<DrawerContent>
Renders the bottom-anchored panel. Self-portals via <DrawerPortal> and renders a drag handle at the top.
<DrawerTitle>
REQUIRED — vaul/Radix surfaces a dev warning when omitted. Render className="sr-only" if no visible heading is needed.
<DrawerTrigger>, <DrawerPortal>, <DrawerOverlay>, <DrawerHandle>, <DrawerHeader>, <DrawerFooter>, <DrawerDescription>, <DrawerClose>
Direct re-exports of the vaul sub-components with the chassis cva applied.
Implementation Notes
- The chassis NEVER assigns a numeric
z-indexto the overlay or content panels — vaul/Radix portals handle stacking. <DrawerContent>self-portals via<DrawerPortal>and renders a<DrawerHandle>at the top automatically — consumers do not need to compose them manually. This matches the shadcndrawerupstream recipe.- Drag-down dismissal is a vaul-native gesture that requires real pointer events; happy-dom-based unit tests cover the open/close lifecycle via Escape and
onOpenChangerather than the drag gesture itself. End-to-end coverage of the swipe gesture lives in the consumer's Playwright suite. shouldScaleBackgrounddefaults totrue(chassis-overrides vaul'sfalse) because the scale-down effect is the primary visual cue distinguishing a Drawer from a Sheet.- Vaul's
DrawerClosebutton forwards toonOpenChange(false)rather than mutating internal state directly — controlled consumers should listen ononOpenChangeand update their localopenstate to honour the close request.