Drawer

A draggable panel anchored to an edge, with the momentum of a native sheet.

Usage

import { Drawer } from "@voila.dev/ui/drawer";
<Drawer.Root direction="right">
	<Drawer.Trigger asChild>
		<Button variant="outline">View project</Button>
	</Drawer.Trigger>
	<Drawer.Content>
		<Drawer.Header>
			<Drawer.Title>Launch review — Saturday</Drawer.Title>
			<Drawer.Description>Kickoff call · Zoom, 14:00.</Drawer.Description>
		</Drawer.Header>
		<Drawer.Footer>
			<Button>Apply to this project</Button>
		</Drawer.Footer>
	</Drawer.Content>
</Drawer.Root>

A panel you can throw. Built on Vaul, so it drags with momentum and snaps like a native sheet — which is why this one uses asChild where the rest of the kit uses render. Choose it over Sheet when the gesture is part of the point, which on touch it usually is. Choose Sheet when the panel is desktop furniture that happens to slide.

Recipes

direction sets the edge. Only top and bottom drawers drag, so showHandle — the grab bar — is meaningful there and noise on the sides; showCloseButton exists for exactly that reason, giving left/right drawers a way out that a handle would otherwise have provided.

overlayClassName styles the backdrop, className the panel. They are separate props because dimming the page and shaping the sheet are different decisions.

API

Drawer.Root

PropTypeDefault
activeSnapPointstring | number | null
autoFocusboolean
childrenReactNode
closeThresholdnumberNumber between 0 and 1 that determines when the drawer should be closed. Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more.
containerHTMLElement | null
defaultOpenbooleanOpened by default, skips initial enter animation. Still reacts to open state changes
direction"bottom" | "left" | "right" | "top"Direction of the drawer. Can be top or bottom, left, right.
disablePreventScrollbooleanWhen set to true prevents scrolling on the document body on mount, and restores it on unmount.
dismissiblebooleanWhen false dragging, clicking outside, pressing esc, etc. will not close the drawer. Use this in comination with the open prop, otherwise you won't be able to open/close the drawer.
fadeFromIndexnumberIndex of a snapPoint from which the overlay fade should be applied. Defaults to the last snap point.
fixedbooleanWhen true, don't move the drawer upwards if there's space, but rather only change it's height so it's fully scrollable when the keyboard is open
handleOnlybooleanWhen true only allows the drawer to be dragged by the <Drawer.Handle /> component.
modalbooleanWhen false it allows to interact with elements outside of the drawer without closing it.
nestedboolean
noBodyStylesbooleanWhen true the body doesn't get any styles assigned from Vaul
onAnimationEnd((open: boolean) => void)Gets triggered after the open or close animation ends, it receives an open argument with the open state of the drawer by the time the function was triggered. Useful to revert any state changes for example.
onClose(() => void)
onDrag((event: PointerEvent<HTMLDivElement>, percentageDragged: number) => void)
onOpenChange((open: boolean) => void)
onRelease((event: PointerEvent<HTMLDivElement>, open: boolean) => void)
openboolean
preventScrollRestorationboolean
repositionInputsbooleanWhen true Vaul will reposition inputs rather than scroll then into view if the keyboard is in the way. Setting it to false will fall back to the default browser behavior.
scrollLockTimeoutnumberDuration for which the drawer is not draggable after scrolling content inside of the drawer.
setActiveSnapPoint((snapPoint: string | number | null) => void)
setBackgroundColorOnScalebooleanWhen false we don't change body's background color when the drawer is open.
shouldScaleBackgroundboolean
snapPoints(string | number)[]Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up. Should go from least visible. Example [0.2, 0.5, 0.8]. You can also use px values, which doesn't take screen height into account.
snapToSequentialPointbooleanDisabled velocity based swiping for snap points. This means that a snap point won't be skipped even if the velocity is high enough. Useful if each snap point in a drawer is equally important.

Plus the DOM props of the element it renders. Source: drawer/components/drawer-root.tsx.

The undescribed props here are Vaul's own — snapPoints, modal, nested and the rest — and cannot be annotated from this repo. Vaul's documentation covers them.

Drawer.Close

PropTypeDefault
asChildboolean

Plus the DOM props of the element it renders. Source: drawer/components/drawer-close.tsx.

Drawer.Content

PropTypeDefault
closeButtonLabelstring"Close"Accessible name for that X. This package ships no translations.
overlayClassNamestringClasses for the backdrop behind the panel. className styles the panel itself.
showCloseButtonbooleantrueRenders an X button on left/right drawers, which have no grab handle.
showHandlebooleantrueDraws the grab bar. Only meaningful on top/bottom drawers, which drag.
asChildboolean
forceMounttrueUsed to force mounting when more control is needed. Useful when controlling animation with React animation libraries.
onCloseAutoFocus((event: Event) => void)Event handler called when auto-focusing on close. Can be prevented.
onEscapeKeyDown((event: KeyboardEvent) => void)Event handler called when the escape key is down. Can be prevented.
onFocusOutside((event: FocusOutsideEvent) => void)Event handler called when the focus moves outside of the DismissableLayer. Can be prevented.
onInteractOutside((event: FocusOutsideEvent | PointerDownOutsideEvent) => void)Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.
onOpenAutoFocus((event: Event) => void)Event handler called when auto-focusing on open. Can be prevented.
onPointerDownOutside((event: PointerDownOutsideEvent) => void)Event handler called when the a pointerdown event happens outside of the DismissableLayer. Can be prevented.

Plus the DOM props of the element it renders. Source: drawer/components/drawer-content.tsx.

Drawer.Description

PropTypeDefault
asChildboolean

Plus the DOM props of the element it renders. Source: drawer/components/drawer-description.tsx.

Drawer.Footer

PropTypeDefault
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <div> prop. Source: drawer/components/drawer-footer.tsx.

Drawer.Handle

Plus every <div> prop. Source: drawer/components/drawer-handle.tsx.

Drawer.Header

PropTypeDefault
renderComponentRenderFn<HTMLProps, {}> | ReactElement<unknown, string | JSXElementConstructor<any>>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a ReactElement or a function that returns the element to render.

Plus every <div> prop. Source: drawer/components/drawer-header.tsx.

Drawer.Overlay

PropTypeDefault
asChildboolean
forceMounttrueUsed to force mounting when more control is needed. Useful when controlling animation with React animation libraries.

Plus the DOM props of the element it renders. Source: drawer/components/drawer-overlay.tsx.

Drawer.Portal

PropTypeDefault
childrenReactNode
containerDocumentFragment | Element | nullSpecify a container element to portal the content into.
forceMounttrueUsed to force mounting when more control is needed. Useful when controlling animation with React animation libraries.

Plus the DOM props of the element it renders. Source: drawer/components/drawer-portal.tsx.

Drawer.Title

PropTypeDefault
asChildboolean

Plus the DOM props of the element it renders. Source: drawer/components/drawer-title.tsx.

Drawer.Trigger

PropTypeDefault
asChildboolean

Plus the DOM props of the element it renders. Source: drawer/components/drawer-trigger.tsx.

Sheet · ResponsiveSheet · ResponsiveDialog · Dialog