Drawer
Bottom drawer with drag to dismiss, snap points, velocity-aware flicks, nesting and a scaled background.
Installs motion.
Installation
npx shadcn@latest add https://neonkit.heyaadi.com/r/drawer.jsonUsage
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer snapPoints={[0.4, 0.85]}>
<DrawerTrigger asChild>
<Button>Find a charger</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Chargers near you</DrawerTitle>
<DrawerDescription>7 stations within 6 km.</DrawerDescription>
</DrawerHeader>
</DrawerContent>
</Drawer>Examples
Content height
Without snapPoints the drawer is as tall as its content. Drag down past a third of it, or flick, to dismiss.
Nested
A drawer opened from a drawer pushes its parent back into a card that always peeks above it.
Scaled background
scaleBackground shrinks the element marked data-drawer-wrapper into a card. Put the attribute on your app's root.
API reference
Drawer
Holds the open state and the active snap point. Renders no element of its own.
| Prop | Type | Default |
|---|---|---|
open | booleanControlled open state. | None |
defaultOpen | booleanInitial state when uncontrolled. | false |
onOpenChange | (open: boolean) => voidCalled by the trigger, DrawerClose, Escape, the overlay and a dismissing drag. | None |
snapPoints | (number | `${number}px`)[]Heights to rest at, lowest first: a fraction of the viewport (0.4) or pixels ("320px"). | None |
activeSnapPoint | SnapPointControlled snap point. Changing it glides the drawer there. | None |
onActiveSnapPointChange | (point: SnapPoint) => voidCalled when a drag, flick or the handle settles on a new stop. | None |
dismissible | booleanWhen false, drags, Escape and the overlay can't close it. | true |
scaleBackground | booleanShrinks [data-drawer-wrapper] behind the drawer. Nested drawers leave it to the outermost one. | false |
color | NeonColorPalette name or hex. Falls back to --neon-accent, then cyan. | inherited |
DrawerContent
The overlay and bottom panel in a native modal <dialog>. Other props go to the panel.
| Prop | Type | Default |
|---|---|---|
showHandle | booleanThe grab handle. With snapPoints it's also a button: Enter steps through the stops, ArrowUp and ArrowDown move one. | true |
bodyClassName | stringClasses for the scrolling body that holds the children. | None |
className | stringClasses for the panel. It's full width up to 672px; pass sm:max-w-md and so on to narrow it. | None |
DrawerTrigger, DrawerClose
Buttons that open and close the drawer. Both accept native button attributes.
| Prop | Type | Default |
|---|---|---|
asChild | booleanMerges the behaviour onto the single child element instead of rendering a button. | false |
DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter
Layout and text parts. Each accepts the native attributes of its element.
| Prop | Type | Default |
|---|---|---|
DrawerTitle | h2Names the drawer through aria-labelledby. | None |
DrawerDescription | pDescribes it through aria-describedby, only while it's rendered. | None |
DrawerFooter | divStacked actions, pushed to the bottom of the panel. | None |
Accessibility
- Drag from anywhere on the panel except inputs and [data-drawer-no-drag]. A drag never fires the click it started on.
- Fully open with scrollable content, the content scrolls; pull down from its top to drag the drawer instead.
- Flicks carry their speed: a quick one moves at least one stop, a hard one downward dismisses.
- Focus lands on the panel rather than the first input, so phones don't raise the keyboard on open, and returns to the trigger on close.
- Escape closes only the topmost drawer. With reduced motion the drawer appears at its stop without sliding.