Drawer
Installation
npx @mielui/svelte add draweryarn dlx @mielui/svelte add drawerbunx @mielui/svelte add drawerUsage
Compose Root, Trigger, Portal, Overlay, and Content. Inside Content, use any of Handle, Header, Title, Description, Body, Footer, and Close. Each part is optional: omit Handle for a plain panel, or move Close into Header. Always give the drawer a Title, or an aria-label on Content.
<script lang="ts">
import { Button } from '@mielui/svelte/components/button';
import * as Drawer from '@mielui/svelte/components/drawer';
let open = $state(false);
</script>
<Drawer.Root bind:open>
<Drawer.Trigger>Open</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Handle />
<Drawer.Header>
<Drawer.Title>Title</Drawer.Title>
<Drawer.Description>Describe what lives here.</Drawer.Description>
</Drawer.Header>
<Drawer.Body>Panel content</Drawer.Body>
<Drawer.Footer>
<Drawer.Close>Cancel</Drawer.Close>
<Button>Save</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>Set direction on Root to bottom (the default), top, left, or right. Top and bottom drawers are centered and capped at a readable width; side drawers fill the height. Put a width class on Content to change the size, such as w-3xl.
Content uses the same frame as Dialog and Sheet. Footer renders below the inner surface wherever you place it, and Close moves to the start of the footer so the other actions line up at the end. The theme's chrome.borders setting chooses single or double framing.
Bind open or use onOpenChange to control visibility. handleOnly limits dragging to the Handle and closeThreshold sets how far the panel must be dragged before it closes. Scrollable content belongs in Body; add data-vaul-no-drag to controls with their own pointer gestures, such as a slider.
Focus moves to the panel when it opens and returns to the trigger when it closes. Escape and outside presses close the drawer unless dismissible is false; keep a Close button for keyboard users. Reduced motion removes the open and settle animations but keeps dragging. Use Sheet when the panel does not need swipe gestures.
Examples
Reading preferences
A settings drawer with handleOnly that restores the saved values when it is dismissed.
Saved: 18 px text, comfortable spacing.
Directions
One drawer opened from each edge. The handle and the swipe direction follow the edge.
Nested
Set nested on a drawer inside another drawer. Its backdrop sits above the parent, and closing it returns focus to its trigger in the parent panel.
Glass surface
Set surface="glass" on Content for a translucent frame with blur. Omit surface to inherit --mielui-surface from the theme, or set surface="solid" to override it. Glass keeps an opaque fallback when backdrop filtering is unavailable and respects reduced-transparency preferences.
Dismissal and focus
Content decides what happens when someone presses outside or hits Escape. interactOutsideBehavior and escapeKeydownBehavior each take close, ignore, defer-otherwise-close, or defer-otherwise-ignore. The two defer values hand the decision to a parent layer first, which is what a nested drawer wants. onInteractOutside and onEscapeKeydown run for each event, and calling preventDefault() there keeps the drawer open that one time.
trapFocus={false} lets Tab leave the drawer. onCloseAutoFocus runs when focus is about to return to the trigger, and preventing it lets you send focus somewhere else. preventOverflowTextSelection stops a text selection that starts inside from spreading onto the page.
Mounting and parts
forceMount on Content and Overlay keeps them in the DOM while closed, for when you run your own exit animation. restoreScrollDelay is the wait in milliseconds before page scrolling comes back after closing, and should be longer than that animation. Portal takes disabled to render the drawer in place instead of at the end of the document.
On phones, repositionInputs on Root moves the drawer so the on-screen keyboard does not cover a focused field. Set it to false to fall back to the browser's own scrolling. Title renders a heading and level picks which one, from 1 to 6.
Trigger, Close, Content, Overlay, Handle, Title, Description, Header, Body and Footer accept class and style, and support bind:element for the rendered DOM node.
API reference
Drawer.Root
childrenSnippet<[]>Content rendered inside.
direction"top" | "bottom" | "left" | "right"Direction of the drawer. Can be
toporbottom,left,right.openBindablebooleanWhether it is open. Bind it to control the state from outside.
Default
falseonOpenChange((open: boolean) => void)Called with the new state whenever it opens or closes.
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.
handleOnlybooleanWhen
trueonly allows the drawer to be dragged by the<Drawer.Handle />component.dismissiblebooleanWhen
falsedragging, clicking outside, pressing esc, etc. will not close the drawer. Use this in combination with theopenprop, otherwise you won't be able to open/close the drawer.Default
truerepositionInputsbooleanWhen
trueVaul will reposition inputs rather than scroll then into view if the keyboard is in the way. Setting it tofalsewill fall back to the default browser behavior.nestedbooleanSet it on a drawer that opens from inside another drawer.
Default
false
Drawer.Body
childrenSnippet<[]>Content rendered inside.
elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Close
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
elementBindableHTMLButtonElement | nullBindable reference to the DOM element.
Default
null
Drawer.Content
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
onInteractOutsideInteractOutsideEventHandlerCallback fired when an outside interaction event completes, which is either a
pointerup,mouseup, ortouchendevent, depending on the user's input device.interactOutsideBehaviorInteractOutsideBehaviorTypeInteract 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.restoreScrollDelaynumber | nullThe delay in milliseconds before the scrollbar is restored after closing the dialog. This is only applicable when using the
childsnippet for custom transitions andpreventScrollistrue. You should set this to a value greater than the transition duration to prevent content from shifting during the transition.onEscapeKeydown((e: KeyboardEvent) => void)Callback fired when escape is pressed.
escapeKeydownBehaviorEscapeBehaviorTypeEscape 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.forceMountbooleanWhether to force mount the component.
preventOverflowTextSelectionbooleanPassing
truewill prevent the overflow of text selection outside the element, provided the element is the top layer.onOpenAutoFocusEventCallbackEvent handler called when auto-focusing on open. Can be prevented.
onCloseAutoFocusEventCallbackEvent handler called when auto-focusing on close. Can be prevented.
trapFocusbooleanWhether focus is trapped within the focus scope.
surface"solid" | "glass"Surface treatment. Omit it to inherit
--mielui-surfacefrom the theme.elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Description
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
elementBindableHTMLParagraphElement | nullBindable reference to the DOM element.
Default
null
Drawer.Footer
childrenSnippet<[]>Content rendered inside.
elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Handle
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Header
childrenSnippet<[]>Content rendered inside.
elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Overlay
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
forceMountbooleanWhether to force mount the component.
elementBindableHTMLDivElement | nullBindable reference to the DOM element.
Default
null
Drawer.Portal
toPortalTargetWhere to portal the content to.
disabledbooleanDisable portalling and render the component inline
childrenSnippet<[]>The children content to render within the portal.
Drawer.Title
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
level1 | 2 | 3 | 4 | 5 | 6The heading level of the dialog title.
elementBindableHTMLHeadingElement | nullBindable reference to the DOM element.
Default
null
Drawer.Trigger
childrenSnippet<[]>Content rendered inside.
idstringId of the element.
stylestring | StyleProperties | nullInline styles for the element.
elementBindableHTMLButtonElement | nullBindable reference to the DOM element.
Default
null
pnpm dlx @mielui/svelte add drawer