Components/Drawer
Drawer
Bottom-anchored panel with a drag-to-dismiss gesture. Designed for mobile-first interactions where a centered modal would be cramped. The grab handle at the top is a real touch target — drag it down past 120px (configurable) to dismiss, or release to snap back.
Installation
npx ajaxui add drawerAction sheet
Native-feeling list of options. Each row is a button with an icon + label. Cancel sits in the footer.
Form drawer
Long inline forms benefit from the full viewport width compared to a centered modal.
Notes
Anatomy
<Drawer> // open/close state owner
<DrawerTrigger/> // any clickable becomes the trigger via asChild
<DrawerContent> // the sheet — captures the drag gesture
<DrawerHeader>
<DrawerTitle/>
<DrawerDescription/>
</DrawerHeader>
<DrawerBody/> // scrollable region
<DrawerFooter/> // action row
</DrawerContent>
</Drawer>Drag mechanics
- Pointer-down anywhere in the top grab region captures the gesture.
- Drag down follows the cursor 1:1 and fades the backdrop proportionally.
- Drag up is rubber-banded (capped at −16px) — only dismissal is committed.
- Release past the threshold dismisses; otherwise snaps back with a 200ms ease.
- Tap-outside still closes, and Escape works via the focus trap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled open state. |
| defaultOpen | boolean | false | Uncontrolled initial state. |
| onOpenChange | (open: boolean) => void | — | Fires whenever the drawer opens or closes. |
| noDrag | boolean (on Content) | false | Disable the drag-to-dismiss gesture. |
| dismissThreshold | number (on Content) | 120 | Pixels of downward drag past which release dismisses. |
| handle | ReactNode (on Content) | — | Custom handle in place of the default pill. |