Skip to Content

Drawer

A panel that slides in from an edge and can be dragged shut. Built on vaul , which is a Radix Dialog underneath.

Live Examples

Live Example
☀️
Copy
View Code
import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose, Button } from '@jarllyng/nostromo' export default function DrawerExample() { return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Open drawer</Button> </DrawerTrigger> <DrawerContent>...

Drag the panel down to dismiss it, or grab the bar at the top.

From the side

Live Example
☀️
Copy
View Code
import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, Button } from '@jarllyng/nostromo' export default function DrawerSide() { return ( <Drawer direction="right"> <DrawerTrigger asChild> <Button variant="outline">Open from the right</Button> </DrawerTrigger> <DrawerContent>...

The grabber is only drawn for a drawer coming from the top or the bottom. A horizontal bar on a side panel would suggest the wrong direction.

Snap points

Live Example
☀️
Copy
View Code
import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, Button } from '@jarllyng/nostromo' export default function DrawerSnap() { const [snap, setSnap] = React.useState<number | string | null>('148px') return ( <Drawer snapPoints={['148px', '355px', 1]} activeSnapPoint={snap} setActiveSnapPoint={setSnap}> <DrawerTrigger asChild> <Button variant="outline">Open part way</Button>...

Import

import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose, } from "@jarllyng/nostromo";

Parts

PartWhat it is
DrawerThe root. Owns direction, snap points and the open state
DrawerTriggerOpens it. Takes asChild
DrawerContentThe panel, with the overlay and the grabber
DrawerHeaderTitle and description, centred for top and bottom
DrawerTitleNames the dialog. Required for it to be announced
DrawerDescriptionDescribes it, announced after the title
DrawerFooterActions, pushed to the bottom
DrawerCloseCloses it. Takes asChild
DrawerHandleThe grabber on its own, for a header of your own design
DrawerNestedUse in place of Drawer for a drawer opened from a drawer

Props

PropTypeDefaultDescription
direction"top" | "bottom" | "left" | "right""bottom"Which edge it comes from
openboolean-Controlled state
onOpenChange(open: boolean) => void-Fires on open and close
snapPoints(number | string)[]-Heights it settles at
activeSnapPointnumber | string | null-Controlled snap point
dismissiblebooleantruefalse blocks drag, Escape and the overlay
modalbooleantruefalse leaves the page interactive
closeThresholdnumber0.25How far to drag before it closes
handleOnlybooleanfalseOnly the grabber starts a drag
shouldScaleBackgroundbooleanfalsePush the page back as it opens

DrawerContent also takes withHandle, default true.

Drawer or Sheet?

They look alike. The difference is the gesture.

SheetDrawer
Opens from an edgeyesyes
Drag to dismissnoyes
Snap pointsnoyes
Grabbernoyes
Extra dependencynonevaul

Sheet is a dialog anchored to an edge: it opens and closes, and that is all. Drawer adds the touch model, which is the mobile convention. If you would not use the drag, use Sheet.

One difference in how they are modal: vaul does not put aria-modal on the dialog. It aria-hides the rest of the page instead, which confines a screen reader the same way.

Positioning comes from a data attribute

direction is a prop on the root, and vaul writes it onto the content as data-vaul-drawer-direction. The styling reads that rather than taking a variant of its own, so the two cannot disagree: the drawer cannot be told to come from the left and then be styled along the bottom.

That is also the hook for your own styles:

<DrawerContent className="data-[vaul-drawer-direction=right]:sm:max-w-md">

Always give it a title

DrawerTitle is what names the dialog. Without one it is announced as “dialog”. If the design has no visible heading, render the title with className="sr-only" rather than leaving it out.

Last updated on