Drawer
A bottom sheet that settles at more than one height — the pattern a phone map uses: a peek showing the headline, a half height for the list, and a full height for the detail, moved between by dragging the grabber or by the keyboard.
snapPoints are fractions of the viewport height, smallest first, and snap is the index it is resting at — bindable, so the app can move it (a search result arrives, so open to half) and can watch the reader move it back. The drag is followed frame by frame and released with its momentum: where it lands is where the gesture was heading, not where the finger happened to stop. Past the smallest point a dismissible drawer closes, which is the gesture everyone already knows. Under prefers-reduced-motion it jumps between heights instead of gliding. The grabber is a real control, not a decoration: an APG window splitter, so ↑/↓ move between the snap points, Home/End reach the extremes, and a screen reader is told which of them it is on. Dragging is never the only way to resize. Modal by default — backdrop, focus trap, the page inert, body scroll locked. modal={false} is the map case: the page behind stays live while the drawer peeks over it.
Overlays · two-way: bind:open bind:snap · portalled to <body> · keyboard contract
<script lang="ts">
import { Drawer } from '@l4st/driftwood';
</script>
| Light | Dark |
|---|---|
![]() |
![]() |
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
open bind: |
boolean |
false |
Opening slides it up to snap. |
snapPoints |
number[] |
[0.35, 0.9] |
The heights it settles at, as fractions of the viewport, smallest first. |
snap bind: |
number |
0 |
Index of the snap point it is resting at. Bindable both ways. |
dismissible |
boolean |
true |
A drag below the smallest snap point closes it. |
modal |
boolean |
true |
Traps focus, inerts the page and dims it. false leaves the page live under a peek. |
title |
string |
— | Names the drawer for assistive tech (aria-labelledby). |
description |
string |
— | One line under the title; also its aria-describedby. |
class |
string |
— | Merged with the component's own classes, last — so yours win. |
handleLabel |
string |
'Drawer height' |
Accessible name of the grabber, which is the resize control. |
closeLabel |
string |
'Close' |
Accessible name of the close button. Hidden entirely when dismissible is false. |
onSnapChange |
(snap: number) => void |
— | Fires with the index it settles at, however it got there. |
children required |
Snippet |
— | The content. |
footer |
Snippet |
— | Actions along the bottom, above the safe area. Put the confirming button last. |
Keyboard
| Key | Does |
|---|---|
| ↑ / ↓ | move between snap points, from the grabber — ↓ at the lowest one closes |
| Home / End | the smallest / tallest snap point |
| Tab / Shift+Tab | cycle within a modal drawer — focus is trapped |
| Escape | close and return focus to the opener |
Notes
Notes
- Snap points are fractions of the viewport, smallest first.
[0.35, 0.9]is a peek and a nearly-full height. Three is usually the most that is worth it: past that, nobody can tell which one they are on. bind:snapruns both ways. The app can move the drawer — a result arrives, so open it to the middle — and can watch the reader move it back.onSnapChangeis the same information as a callback, for the cases where a binding is awkward.modal={false}is the map case. The default drawer dims the page, traps focus and locks the page's scroll, which is what a drawer over a form wants. A drawer over something the reader is still using — a map, a canvas, a video — should not do any of that. A non-modal drawer still closes on Escape and still moves focus into itself when it opens; it just does not hold on to it.- The grabber is a control, not a decoration. It is an APG window splitter:
Tab reaches it, ↑ and ↓ move between the snap points, Home and End reach the
ends, and ↓ at the lowest one closes a
dismissibledrawer. A pointer drag is an enhancement over that, never the only way — dragging is a gesture plenty of people cannot make. - The drag is released with its momentum. Where it lands is where the
gesture was heading a tenth of a second later, so a flick carries past the
nearest stop and a slow drag does not. Under
prefers-reduced-motionit jumps between heights with no glide at all. - The backdrop fades with the height. A peek dims the page less than a full height does, so the drag has a visible consequence beyond the panel itself.
dismissible={false}means it stays. No close button, no Escape, and a drag below the smallest snap point springs back — for the drawer that is the screen rather than something over it.
Overlays · Dialog · Sheet · Tooltip · Popover
driftwood
Layout
Actions
Forms
- Field
- Input
- FileInput
- FileList
- Textarea
- CodeEditor
- Combobox
- RadioGroup
- NumberInput
- InlineEdit
- Label
- Select
- MultiSelect
- DatePicker
- TagsInput
- Checkbox
- Switch
- Slider
Feedback
Charts
Overlays
Navigation
Generated from the source by pnpm wiki · repository · v0.24.0

