Manual UI Templates Drawer

Drawer

elements man ui/templates/drawer

A panel that slides from an edge and can be dragged away, with snap points and a scaled background.

Example
import { Drawer, DrawerBody, DrawerFooter, DrawerClose } from "@elements/ui/drawer"; import { Button } from "@elements/ui/button"; <Drawer title="Move goal" description="Set your daily activity goal."> <Button slot="trigger">Open drawer</Button> <DrawerBody>...</DrawerBody> <DrawerFooter> <Button variant="primary" onclick={() => submit(goal)}>Submit</Button> <DrawerClose><Button>Cancel</Button></DrawerClose> </DrawerFooter> </Drawer> <!-- snap points: snapPoint is two-way --> <Drawer snapPoints={[0.3, 0.6, 1]} snapPoint={state.snap}>...</Drawer>

Parts

Part What it is
Drawer The root: owns open and snapPoint, renders the trigger slot and, while open, the <dialog> overlay, backdrop, handle and panel.
DrawerHeader Stacks a title and description; centered on bottom and top drawers below 48rem.
DrawerTitle An <h2>; names the drawer (aria-labelledby).
DrawerDescription A <p>; describes the drawer (aria-describedby).
DrawerBody The content between header and footer; scrolls when tall, and is then a Tab stop so the keyboard can scroll it. Dragging starts only once it is scrolled to the top.
DrawerFooter Actions pinned to the bottom, stacked full width.
DrawerClose Wraps any element; clicking it closes this drawer.
DrawerWrapper Wrap the page in it; a drawer with scaleBackground scales it back while open and as it is dragged.

Attributes

Drawer

Attribute Type Default Description
open boolean (two-way) false Whether the drawer is open. Bind it to control it; the drawer writes false back when it closes itself. Omit it and the trigger slot opens it.
direction "bottom" | "top" | "left" | "right" "bottom" The edge the drawer slides from and is dragged back to.
title string "" Renders a DrawerTitle in a header and names the drawer.
description string "" Renders a DrawerDescription under the title.
label string "" aria-label for a drawer with no visible title.
modal boolean | "trap-focus" true true: backdrop, inert page, focus trap, scroll lock. "trap-focus": focus is trapped, but there is no backdrop or lock and the page takes clicks. false: the page stays usable (combine with snap points for a map-style sheet).
dismissible boolean true false: dragging, Escape and a press outside never close it. Close it from your own control.
dismissOnOutside boolean true A press outside closes it.
handle boolean true Shows the grab handle at the inner edge.
handleOnly boolean false Only the handle starts a drag; the content scrolls and selects freely.
handleCycles boolean true A click on the handle moves to the next snap point, and from the last closes. false turns that off.
closeButton boolean false Shows an X button in the corner.
snapPoints (number | string)[] [] Heights the drawer rests at, smallest first: fractions of the viewport (0.5), px (320 or "320px") or rem ("20rem").
snapPoint number | string | null (two-way) null The active snap point; null starts at the first. Two-way: the drawer writes the point it settles on, and goes back to the first point once a close finishes, so every open starts there.
fadeFromIndex number -1 The snap point index at which the backdrop is fully shown; it fades in from the point before. -1 is the last.
snapToSequentialPoints boolean false A fast fling moves one snap point at a time instead of skipping to the first or last.
closeThreshold number 0.25 Fraction of the drawer's size a slow drag must pass to close it on release.
swipeArea boolean false Renders an invisible strip on the drawer's edge. A swipe from it opens the drawer, which follows the finger; let go past half its size or with a flick and it stays open, otherwise it closes again.
swipeAreaDirection "up" | "down" | "left" | "right" | "" "" The swipe that opens the drawer from the swipe area. The strip sits on the edge that swipe starts from. Empty: toward the middle from the drawer's own edge.
fixed boolean false With repositionInputs and the on-screen keyboard up, the panel keeps its top where it is and ends at the keyboard instead of moving up, when there is room above it.
noBodyStyles boolean false A modal drawer sets no scroll lock styles on the page; it is otherwise modal.
swipeAreaDisabled boolean false Keeps the swipe area rendered, marked data-disabled, but a swipe from it opens nothing and the page under it takes touches.
repositionInputs boolean true Keeps a bottom drawer above the on-screen keyboard while a text field inside has focus. Off, the browser handles it.
scrollLockTimeout number 100 ms after scrolling content inside during which a drag does not start.
scaleBackground boolean false Scales every DrawerWrapper back while open, following the drag.
initialFocus DialogFocus "" CSS selector of the element to focus on open, or a function of the interaction type (keyboard, mouse, touch, pen) that returns an element, a selector, true for the default or false to leave focus. Default: [autofocus], else the first focusable element that is not a link, else the panel.
returnFocus boolean true Return focus to the trigger (or whatever had focus) when it closes.
finalFocus DialogFocus "" CSS selector of the element to focus on close, in place of the trigger, or a function of the interaction type that returns an element, a selector, true for the trigger or false to leave focus.
class string "" Extra classes on the panel.
onOpenChange (open: boolean, reason?: DrawerChangeReason, details?: DrawerChangeDetails) => void () => {} Called when the drawer opens or closes itself, with the reason: trigger-press, outside-press, escape-key, close-press, swipe (a drag, or the swipe area), close-watcher (a platform close request such as Android's back gesture), focus-out or none. The third argument is the change details: { reason, event (the DOM event behind the change, or null), trigger (the trigger element on trigger-press) }.
onSnapPointChange (point: number | string, reason?: DrawerChangeReason) => void () => {} Called when a drag or the handle settles on a different snap point, with the reason: swipe for a drag, trigger-press for a handle click, none for the return to the first point after a close.
onDrag (e: PointerEvent | null, progress: number) => void () => {} Called on every drag move; progress is 0 (open) to 1 (at the closed edge).
onRelease (e: PointerEvent | null, open: boolean) => void () => {} Called when a drag ends; open is whether the drawer stays open.
onOpenChangeComplete (open: boolean) => void () => {} Called after the enter animation (true) or once the drawer has left the page after its exit (false).
onEscapeKeyDown (e: KeyboardEvent) => void () => {} Called on Escape before closing; e.preventDefault() keeps it open, and e.allowPropagation() lets the Escape reach the page's own key handlers too.
onPointerDownOutside (e: PointerEvent) => void () => {} Called on a press outside before closing; e.preventDefault() keeps it open.
onInteractOutside (e: Event) => void () => {} Called before a press or focus outside dismisses it; e.preventDefault() keeps it open.
onOpenAutoFocus (e: Event) => void () => {} Called before focus moves in on open; e.preventDefault() leaves focus to the handler.
onCloseAutoFocus (e: Event) => void () => {} Called before focus returns on close; e.preventDefault() leaves focus to the handler.

DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter, DrawerWrapper

Attribute Type Default Description
class string "" Extra classes.

Slots

Slot Content
trigger The element that opens the drawer. It gets aria-haspopup, aria-expanded, aria-controls and data-state.
default The drawer's content, below the handle and header.

Keyboard

Keys Action
Space / Enter On the trigger: opens the drawer.
Tab Moves to the next focusable element inside; wraps from the last to the first.
Shift + Tab Moves to the previous focusable element; wraps.
Enter / Space On the handle (with snap points): the next snap point, and from the last a close, as a click does.
Arrow toward open / toward the edge On the handle: one snap point up or down (ArrowUp and ArrowDown on a bottom drawer); never closes.
Home / End On the handle: the first or last snap point.
PageUp / PageDown, arrows, Space On a DrawerBody that scrolls (a Tab stop while it overflows): scroll it.
Escape Closes the drawer (unless dismissible is false or onEscapeKeyDown prevents it). With a drag in progress, cancels the drag.

Data attributes

Attribute Values On Description
data-open present .dialog-overlay, .drawer-backdrop, .drawer While open; the same moment as data-state="open".
data-closed, data-ending-style present .dialog-overlay, .drawer-backdrop, .drawer During the exit animation; the same moment as data-state="closed".
data-starting-style present .dialog-overlay, .drawer-backdrop, .drawer For the first frame after the overlay mounts, so a CSS transition can start from it.
data-popup-open present trigger While open; the same moment as the trigger's data-state="open".
data-state "open" | "closed" trigger, .dialog-overlay, .drawer-backdrop, .drawer closed during the exit animation.
data-direction "bottom" | "top" | "left" | "right" .drawer
data-swipe-direction "down" | "up" | "left" | "right" .drawer The edge the drawer sits on.
data-swipe-axis "x" | "y" .drawer
data-swiping present .drawer, .drawer-backdrop, .drawer-wrapper While a drag is moving the drawer.
data-snap-points present .drawer The drawer has snap points.
data-drawer-scroll-stop present .drawer-body The body overflows and is a Tab stop (tabindex 0), a region named after the title.
data-expanded present .drawer At the last (largest) snap point.
data-keyboard present .drawer Lifted above the on-screen keyboard (repositionInputs); --drawer-keyboard holds the covered height.
data-nested-drawer-open present .drawer A drawer opened from inside this one is open; this one steps back.
data-active present .drawer-wrapper Scaled back by a scaleBackground drawer.
data-drawer-no-drag set by you any element inside A press here never starts a drag. Set it to x or y to block only that axis.
--drawer-x, --drawer-y px .drawer The panel's offset from its resting place, while dragging and for snap points.
--drawer-progress 0 to 1 .drawer, .drawer-wrapper 1 open, 0 closed; follows the drag.
--nested-drawers count .drawer Drawers open inside this one.
data-nested-drawer-swiping present .drawer A drawer opened from inside this one is being dragged; this one follows it forward (--drawer-nested-progress, 1 to 0).
--drawer-frontmost-height px .drawer The height of the drawer open inside this one.
data-nested present .drawer-overlay This drawer was opened from inside another.
data-swipe-dismiss present .drawer A swipe closed it; set through the exit animation.
--drawer-swipe-strength 0.1 to 1 .drawer Set when a swipe closes it: a faster fling gives a smaller value and a shorter exit.
--drawer-swipe-movement-x, --drawer-swipe-movement-y px .drawer How far a drag has moved the panel from where it rests.
--drawer-swipe-progress 0 to 1 .drawer, .drawer-backdrop 0 at rest, 1 at the closed edge.
--drawer-height px .drawer The panel's height.
--drawer-snap-point-offset px .drawer How far the active snap point leaves the panel translated toward its edge.
--drawer-inset length .drawer (set by the caller) Floats the panel that far off the viewport edges, with every corner rounded. 0 by default.
--drawer-overlay-min-opacity 0 to 1 .drawer or .drawer-backdrop (set by the caller) A floor for the backdrop's opacity, at every snap point and during a drag. Unset, 0.5 with snap points and 0 without.
data-swipe-opened present .drawer, .drawer-backdrop While a drawer the swipe area opened is open: it entered with the finger, not its enter animation.
data-disabled present .drawer-swipe-area swipeAreaDisabled is set.
data-slot "drawer-overlay" | "drawer-content" .drawer-backdrop, .drawer The part's name.
data-fixed present .drawer fixed is keeping the panel's top in place above the keyboard; --drawer-fixed-top holds it.
data-swiping present .drawer-swipe-area While a swipe on the swipe area runs.
data-swipe-direction "up" | "down" | "left" | "right" .drawer-swipe-area The swipe that opens the drawer.
--drawer-bleed-background color .drawer Set it to change the fill past the panel's edge, shown when a drag lifts the panel past fully open. Defaults to the panel background.

Accessibility

  • The overlay is a native <dialog> opened with showModal(): role dialog, the top layer, and an inert page behind it.
  • aria-modal="true", aria-labelledby (the title) and aria-describedby (the description) are set on it.
  • Focus moves in on open, is trapped while modal and returns to the trigger on close.
  • Dragging is a pointer convenience: Escape, a press outside, DrawerClose and the optional close button do the same from the keyboard. Without snap points the handle is decorative (aria-hidden). With them it is a button named for what Enter does (Expand drawer, Close drawer), in the Tab order, and the arrows, Home and End move between snap points.
  • A drawer resting at a low snap point never focuses a control below the viewport: focus goes to the first control it shows, or the panel.

See also