Drawer
Bottom-sheet drawer built on vaul. Mobile-native feel: drag-to-dismiss, snap points, body-scale-on-open. Use for quick mobile actions, filters, pickers.
Installation
pnpm
pnpm dlx @hex-core/cli add drawerAPI Reference
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state |
defaultOpen | boolean | false | Default open state |
onOpenChange | function | — | Callback when open state changes: (open: boolean) => void |
shouldScaleBackground | boolean | true | Scale the <body> element when the drawer opens (creates depth) |
snapPoints | object | — | Array of snap positions ('40%', 400, '100%') — defines resting heights the user can snap to |
activeSnapPoint | object | — | Controlled active snap point value (matches one entry in snapPoints) |
closeThreshold | number | 0.25 | Fraction of height the user must drag down to close (0..1) |
dismissible | boolean | true | Allow drag-to-dismiss and outside-click-to-dismiss |
AI Guidance
When to use
Use on mobile-first or mobile-primary UX when a native app-like bottom sheet matters. Good for filters, quick pickers, confirm-then-do flows, or anywhere a user expects drag-to-dismiss.
When not to use
Don't use on desktop-primary UIs (use Dialog or Sheet). Don't use for side navigation (use Sheet). Don't use for transient info (use Popover or Tooltip). Don't use when you must prevent dismissal — drawers invite drag-down.
Common mistakes
- Forgetting DrawerTitle — vaul/Radix warn and screen readers announce an unnamed dialog
- Placing long forms inside a drawer without snap points — content gets cramped
- Disabling shouldScaleBackground when the background context-cue matters for UX
- Wrapping DrawerContent in Portal yourself — DrawerContent already portals via DrawerPortal
Accessibility
vaul delegates to Radix Dialog: focus trap, Escape to close, aria-labelledby/describedby wired to DrawerTitle/Description. The top handle is decorative (aria-hidden).
Token budget: 700