Overlays

Drawer

A modal panel that slides in from an edge of the screen and can be swiped away.

Installation

pnpm dlx @mielui/svelte add drawer

Usage

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.

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

  • children
    Snippet<[]>

    Content rendered inside.

  • direction
    "top" | "bottom" | "left" | "right"

    Direction of the drawer. Can be top or bottom, left, right.

  • open Bindable
    boolean

    Whether it is open. Bind it to control the state from outside.

    Default false

  • onOpenChange
    ((open: boolean) => void)

    Called with the new state whenever it opens or closes.

  • closeThreshold
    number

    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

    When true only allows the drawer to be dragged by the <Drawer.Handle /> component.

  • dismissible
    boolean

    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.

    Default true

  • repositionInputs
    boolean

    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

    Set it on a drawer that opens from inside another drawer.

    Default false

Drawer.Body

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Close

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • element Bindable
    HTMLButtonElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Content

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • onInteractOutside
    InteractOutsideEventHandler

    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

    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

    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)

    Callback fired when escape is pressed.

  • escapeKeydownBehavior
    EscapeBehaviorType

    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

    Whether to force mount the component.

  • preventOverflowTextSelection
    boolean

    Passing true will prevent the overflow of text selection outside the element, provided the element is the top layer.

  • onOpenAutoFocus
    EventCallback

    Event handler called when auto-focusing on open. Can be prevented.

  • onCloseAutoFocus
    EventCallback

    Event handler called when auto-focusing on close. Can be prevented.

  • trapFocus
    boolean

    Whether focus is trapped within the focus scope.

  • surface
    "solid" | "glass"

    Surface treatment. Omit it to inherit --mielui-surface from the theme.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Description

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • element Bindable
    HTMLParagraphElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Footer

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Handle

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Header

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Overlay

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • forceMount
    boolean

    Whether to force mount the component.

  • element Bindable
    HTMLDivElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Portal

  • to
    PortalTarget

    Where to portal the content to.

  • disabled
    boolean

    Disable portalling and render the component inline

  • children
    Snippet<[]>

    The children content to render within the portal.

Drawer.Title

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • level
    1 | 2 | 3 | 4 | 5 | 6

    The heading level of the dialog title.

  • element Bindable
    HTMLHeadingElement | null

    Bindable reference to the DOM element.

    Default null

Drawer.Trigger

  • children
    Snippet<[]>

    Content rendered inside.

  • id
    string

    Id of the element.

  • style
    string | StyleProperties | null

    Inline styles for the element.

  • element Bindable
    HTMLButtonElement | null

    Bindable reference to the DOM element.

    Default null