Drawer
Installation
Usage
Compose Root, Trigger, Portal, Overlay, and Content. Inside Content, add any combination of Handle, Header, Title, Description, Body, Footer, and Close. Regions are independent. Top and bottom panels span the viewport; constrain individual regions to keep their content readable. You can omit Handle for a plain swipeable panel, move Close into Header, or restyle Content with class. Always provide Title or an aria-label on Content.
Use bind:open or onOpenChange to control visibility. Set direction to bottom, top, left, or right. handleOnly restricts dragging to the handle; closeThreshold controls the dismissal distance.
Drawers are modal. Focus enters the panel and returns to the trigger when it closes. Escape and outside interaction dismiss it unless dismissible is false. Include a Close button for keyboard users.
Set nested on a drawer inside another drawer. Its backdrop appears above the parent. Closing it returns focus to its trigger in the parent panel. Native regions support bind:element. Content accepts focus and outside-interaction callbacks.
Handle supports dragging; use Close for keyboard dismissal. Scrollable content belongs in Body. Add data-vaul-no-drag to custom controls that need their own pointer gestures. Snap-point expansion is not part of the supported API.
Reduced motion removes opening and settling animations while preserving dragging. Use Sheet when the panel does not need swipe gestures.
Nested review
Open the details from the review panel. Close the details to return to the review; each drawer has its own handle and close action.
API reference
Drawer.Root
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
direction | "left" | "right" | "top" | "bottom" | undefined Direction of the drawer. Can be `top` or `bottom`, `left`, `right`. | — |
open Bindable | boolean | undefined | false |
onOpenChange | ((open: boolean) => void) | undefined | — |
closeThreshold | number | undefined Number 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. | — |
handleOnly | boolean | undefined When `true` only allows the drawer to be dragged by the `<Drawer.Handle />` component. | — |
dismissible | boolean | undefined When `false` dragging, clicking outside, pressing esc, etc. will not close the drawer. Use this in combination with the `open` prop, otherwise you won't be able to open/close the drawer. | true |
repositionInputs | boolean | undefined When `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. | — |
nested | boolean | undefined | false |
Drawer.Body
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Close
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
element Bindable | HTMLButtonElement | null | undefined | null |
Drawer.Content
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
onInteractOutside | InteractOutsideEventHandler | undefined Callback fired when an outside interaction event completes, which is either a `pointerup`, `mouseup`, or `touchend` event, depending on the user's input device. | — |
interactOutsideBehavior | InteractOutsideBehaviorType | undefined Interact outside behavior type. `close`: Closes the element immediately. `defer-otherwise-close`: Delegates the action to the parent element. If no parent is found, it closes the element. `defer-otherwise-ignore`: Delegates the action to the parent element. If no parent is found, nothing is done. `ignore`: Prevents the element from closing and also blocks the parent element from closing in response to an outside interaction. | — |
restoreScrollDelay | number | null | undefined The delay in milliseconds before the scrollbar is restored after closing the dialog. This is only applicable when using the `child` snippet for custom transitions and `preventScroll` is `true`. You should set this to a value greater than the transition duration to prevent content from shifting during the transition. | — |
onEscapeKeydown | ((e: KeyboardEvent) => void) | undefined Callback fired when escape is pressed. | — |
escapeKeydownBehavior | EscapeBehaviorType | undefined Escape behavior type. `close`: Closes the element immediately. `defer-otherwise-close`: Delegates the action to its parent component that has an escape keydown handler. If no parent is found, it closes the element. `defer-otherwise-ignore`: Delegates the action to the parent element. If no parent is found, nothing is done. `ignore`: Prevents the element from closing and also blocks the parent element from closing in response to an escape key press. | — |
forceMount | boolean | undefined Whether to force mount the component. | — |
preventOverflowTextSelection | boolean | undefined Passing `true` will prevent the overflow of text selection outside the element, provided the element is the top layer. | — |
onOpenAutoFocus | EventCallback | undefined Event handler called when auto-focusing on open. Can be prevented. | — |
onCloseAutoFocus | EventCallback | undefined Event handler called when auto-focusing on close. Can be prevented. | — |
trapFocus | boolean | undefined Whether focus is trapped within the focus scope. | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Description
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
element Bindable | HTMLParagraphElement | null | undefined | null |
Drawer.Footer
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Handle
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Header
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Overlay
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
forceMount | boolean | undefined Whether to force mount the component. | — |
element Bindable | HTMLDivElement | null | undefined | null |
Drawer.Portal
| Prop | Type | Default |
|---|---|---|
to | PortalTarget | undefined Where to portal the content to. | — |
disabled | boolean | undefined Disable portalling and render the component inline | — |
children | Snippet<[]> | undefined The children content to render within the portal. | — |
Drawer.Title
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
level | 1 | 2 | 3 | 4 | 5 | 6 | undefined The heading level of the dialog title. | — |
element Bindable | HTMLHeadingElement | null | undefined | null |
Drawer.Trigger
| Prop | Type | Default |
|---|---|---|
children | Snippet<[]> | undefined | — |
id | string | undefined | — |
style | string | StyleProperties | null | undefined | — |
element Bindable | HTMLButtonElement | null | undefined | null |
pnpm dlx @mielui/svelte add drawer