Drawer
elements man ui/templates/drawerA panel that slides from an edge and can be dragged away, with snap points and a scaled background.
Exampleimport { 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
- Button (opens in a new tab): A button or a link that looks like one, in six variants and four sizes.
- AlertDialog (opens in a new tab): A modal that interrupts with something important and waits for an answer.
- Sheet (opens in a new tab): A dialog that slides in from an edge of the screen, for content that complements the page.