Drawer
Accessible, unstyled side drawer (sheet) built on native <dialog> and the Dialog contract.
| Package | @vizejs/ui/drawer |
| Maturity | stable |
| Own the source | vize lib pull drawer |
| Requires | context, controllable-state, dialog, dismissable-layer, focus-scope, id, scroll-lock |
| Aliases | sheet, bottom sheet, side sheet, off-canvas, vaul |
| Covers | HTML dialog element, WAI-ARIA dialog pattern, Vaul Drawer, Radix Dialog sheet recipes |
Usage
import { Drawer, DrawerRoot, DrawerContent, DrawerHandle } from "@vizejs/ui/drawer";
Or copy the source into your project with vize lib pull drawer (see Source Distribution).
API
Drawer
Source: src/families/overlays/drawer/drawer-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned Drawer base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Show the native <dialog> with showModal() (top layer, inert page, ::backdrop). |
side |
DrawerSide |
"bottom" |
Viewport edge the drawer is attached to and dragged toward to dismiss. |
snapPoints |
readonly DrawerSnapPoint[] |
undefined |
Resting positions: fractions in (0, 1] of the drawer size or "<number>px" visible sizes. undefined means a single fully open position. |
activeSnapPoint |
DrawerSnapPoint | null |
undefined |
Controlled active snap point. undefined selects uncontrolled behavior. |
defaultActiveSnapPoint |
DrawerSnapPoint |
undefined |
Initial snap point for uncontrolled use. undefined selects the first snap point. |
dismissible |
boolean |
true |
Whether Escape, backdrop presses, outside presses, and drag gestures may dismiss. |
closeThreshold |
number |
0.25 |
Fraction of the size still visible at the lowest snap point that a drag must pass to dismiss. |
velocityThreshold |
number |
0.5 |
Release velocity in px/ms toward the dismiss direction that flicks the drawer closed. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the Drawer requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
update:activeSnapPoint |
[value: DrawerSnapPoint | null] |
Fired when the Drawer requests a controlled snap point. |
snap-point-change |
[ value: DrawerSnapPoint | null, previous: DrawerSnapPoint | null, nativeEvent: Event | null, ] |
Fired after any distinct snap point request. |
drag-start |
[nativeEvent: PointerEvent] |
Fired once a pointer has moved past the drag slop. |
drag-end |
[event: DrawerDragEndEvent] |
Fired when a started drag gesture ends. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DrawerSlotState |
Compound Drawer children. Receives the current open, snap, and drag state. |
Exposed
| Member | Type | Description |
|---|---|---|
activeSnapPoint |
— | |
close |
— | |
contentId |
— | |
descriptionId |
— | |
dragging |
— | |
id |
— | |
modal |
— | |
open |
— | |
openDrawer |
— | |
setOpen |
— | |
side |
— | |
snapPoints |
— | |
snapTo |
— | |
state |
— | |
titleId |
— | |
toggle |
— |
DrawerRoot
Source: src/families/overlays/drawer/drawer-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string | null |
undefined |
Consumer-owned Drawer base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Show the native <dialog> with showModal() (top layer, inert page, ::backdrop). |
side |
DrawerSide |
"bottom" |
Viewport edge the drawer is attached to and dragged toward to dismiss. |
snapPoints |
readonly DrawerSnapPoint[] |
undefined |
Resting positions: fractions in (0, 1] of the drawer size or "<number>px" visible sizes. undefined means a single fully open position. |
activeSnapPoint |
DrawerSnapPoint | null |
undefined |
Controlled active snap point. undefined selects uncontrolled behavior. |
defaultActiveSnapPoint |
DrawerSnapPoint |
undefined |
Initial snap point for uncontrolled use. undefined selects the first snap point. |
dismissible |
boolean |
true |
Whether Escape, backdrop presses, outside presses, and drag gestures may dismiss. |
closeThreshold |
number |
0.25 |
Fraction of the size still visible at the lowest snap point that a drag must pass to dismiss. |
velocityThreshold |
number |
0.5 |
Release velocity in px/ms toward the dismiss direction that flicks the drawer closed. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the Drawer requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
update:activeSnapPoint |
[value: DrawerSnapPoint | null] |
Fired when the Drawer requests a controlled snap point. |
snap-point-change |
[ value: DrawerSnapPoint | null, previous: DrawerSnapPoint | null, nativeEvent: Event | null, ] |
Fired after any distinct snap point request. |
drag-start |
[nativeEvent: PointerEvent] |
Fired once a pointer has moved past the drag slop. |
drag-end |
[event: DrawerDragEndEvent] |
Fired when a started drag gesture ends. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DrawerSlotState |
Compound Drawer children. Receives the current open, snap, and drag state. |
Exposed
| Member | Type | Description |
|---|---|---|
open |
boolean |
Whether the drawer is open. |
modal |
boolean |
Whether the drawer is shown as a modal <dialog>. |
state |
DrawerState |
Stable state token for styling and tests. |
side |
DrawerSide |
Viewport edge the drawer is attached to. |
activeSnapPoint |
DrawerSnapPoint | null |
Snap point the drawer currently rests on, or null without snap points. |
dragging |
boolean |
Whether a drag gesture is in progress. |
id |
string |
Root-owned base id. |
contentId |
string |
Id of the rendered <dialog>. |
titleId |
string |
Default id consumed by DrawerTitle. |
descriptionId |
string |
Default id consumed by DrawerDescription. |
snapPoints |
readonly DrawerSnapPoint[] |
Configured snap points in consumer order. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request a specific open value and report whether it differs. |
openDrawer |
(event?: Event | null) => boolean |
Request the open state. |
close |
(event?: Event | null) => boolean |
Request the closed state. |
toggle |
(event?: Event | null) => boolean |
Request the opposite open state. |
snapTo |
(snapPoint: DrawerSnapPoint, event?: Event | null) => boolean |
Request a snap point and report whether it differs. Unknown values are ignored. |
DrawerContent
Source: src/families/overlays/drawer/drawer-content.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
forceMount |
boolean |
false |
Render slot contents while the drawer is closed. |
autoFocus |
boolean |
true |
Move focus into the drawer when it opens. |
restoreFocus |
boolean |
true |
Restore focus to the trigger (or previous focus) when the drawer closes. |
lockScroll |
boolean |
true |
Lock document scroll while a modal drawer is open. |
closeOnEscape |
boolean |
true |
Let Escape (and the native cancel event) request dismissal. |
closeOnBackdropPointerDown |
boolean |
true |
Let a press on the modal ::backdrop request dismissal. |
closeOnPointerDownOutside |
boolean |
true |
Let a press outside a non-modal drawer request dismissal. |
dragFromContent |
boolean |
true |
Start drag gestures anywhere on the content, not only on DrawerHandle. |
ariaLabel |
string |
undefined |
Accessible name when no visible title supplies one. |
ariaLabelledby |
string | null |
undefined |
Space-separated ids that label the drawer. null omits the default title id. |
ariaDescribedby |
string | null |
undefined |
Space-separated ids that describe the drawer. null omits the default description id. |
Events
| Event | Payload | Description |
|---|---|---|
open-auto-focus |
[event: DrawerAutoFocusEvent] |
Fired before automatic entry focus is applied. |
close-auto-focus |
[event: DrawerAutoFocusEvent] |
Fired before automatic focus restoration is applied. |
escape-key-down |
[event: DrawerEscapeKeyDownEvent] |
Fired before Escape requests dismissal. |
pointer-down-outside |
[event: DrawerPointerDownOutsideEvent] |
Fired before an outside pointer-down requests dismissal of a non-modal drawer. |
backdrop-pointer-down |
[event: DrawerBackdropPointerDownEvent] |
Fired before a modal ::backdrop press requests dismissal. |
dismiss |
[event: DrawerDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DrawerSlotState |
Drawer content. Receives the current open, snap, and drag state. |
Exposed
| Member | Type | Description |
|---|---|---|
open |
boolean |
Whether the drawer is open. |
modal |
boolean |
Whether the drawer is shown as a modal <dialog>. |
state |
DrawerState |
Stable state token for styling and tests. |
side |
DrawerSide |
Viewport edge the drawer is attached to. |
activeSnapPoint |
DrawerSnapPoint | null |
Snap point the drawer currently rests on, or null without snap points. |
dragging |
boolean |
Whether a drag gesture is in progress. |
element |
HTMLDialogElement | null |
Rendered native dialog element. |
snapOffset |
number |
Resolved snap offset in px toward the dismiss direction. |
dragOffset |
number |
Current drag offset in px toward the dismiss direction. |
focusContent |
(options?: FocusOptions) => void |
Move focus to the dialog element. |
DrawerHandle
Source: src/families/overlays/drawer/drawer-handle.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Remove the handle from activation and sequential keyboard focus. Dragging from content still works. |
ariaLabel |
string |
"Resize drawer" |
Accessible name of the handle button. |
Events
| Event | Payload | Description |
|---|---|---|
click |
[nativeEvent: MouseEvent] |
Fired before the handle steps to the next snap point. Call preventDefault() to keep it. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
DrawerSlotState |
Optional grip contents. Receives the current Drawer state. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the handle. |
Behavior
Normative state x input -> outcome table for @vizejs/ui/drawer (drawer-root.vue,
drawer-content.vue, drawer-handle.vue). The trigger, title, description, and close parts are
the Dialog SFCs (dialog-trigger.vue, dialog-title.vue, dialog-description.vue,
dialog-close.vue) re-exported as DrawerTrigger, DrawerTitle, DrawerDescription, and
DrawerClose; DrawerRoot provides the shared Dialog context they consume. Every row names the
test that proves it (drawer.test.ts, drawer-ssr.test.ts, or drawer.types.test-d.ts).
| Surface | Contract |
|---|---|
| Native | DrawerContent renders a native <dialog>: showModal() when open and modal (top layer, inert page, ::backdrop), show() when non-modal, close() when closed. |
| State | DrawerRoot supports uncontrolled defaultOpen / controlled open and uncontrolled defaultActiveSnapPoint / controlled activeSnapPoint. |
| Snap | Snap points are fractions in (0, 1] of the drawer size or "<number>px" visible sizes; invalid values throw VIZE_UI_DRAWER_SNAP_POINT. |
| Styling | After mount, content publishes --vize-drawer-snap-offset, --vize-drawer-drag-offset, and --vize-drawer-offset (px toward the dismiss edge). |
| Data | data-vize-ui, part, data-state, data-side, data-modal, data-snap-point, and data-dragging form the styling contract; no CSS ships. |
| SSR | Server markup never carries the open attribute or offset variables; the dialog is opened only after hydration, so markup hydrates without mismatches. |
| State x input | Outcome | Proven by |
|---|---|---|
| closed x trigger click | Opens the modal <dialog>, wires aria-labelledby/aria-describedby and trigger aria-controls, focuses inside. |
opens a labelled native modal dialog from the trigger and restores focus on close |
open x DrawerClose click |
Restores focus to the trigger, then closes the native dialog. | opens a labelled native modal dialog from the trigger and restores focus on close |
open x native cancel event or Escape keydown |
cancel is always prevented and routed through state; dismissal emits dismiss with escape-key. |
Escape and the native cancel event dismiss through Drawer state |
open x prevented escape-key-down, or dismissible=false |
The drawer stays open; the native cancel is still prevented. |
escape-key-down is preventable and non-dismissible drawers ignore cancel |
controlled open x trigger click |
Emits update:open and waits for the parent before calling showModal(). |
controlled open state waits for the parent |
open modal x press on ::backdrop outside the box |
Emits preventable backdrop-pointer-down, then dismisses with reason backdrop; presses inside the box do not. |
backdrop presses outside the dialog box dismiss unless prevented |
| open non-modal x outside press | Uses show(); an outside pointer-down dismisses through the dismissable layer. |
non-modal drawers use show() and dismiss on outside presses |
open x native close (for example form method="dialog") |
Mirrors the close into state with update:open(false). |
a native close (form method=dialog) is mirrored into state |
| open x drag past the 4px slop | Sets data-dragging, emits drag-start, publishes drag offsets; release picks the nearest snap point after velocity projection and emits update:activeSnapPoint and drag-end. |
dragging publishes offsets and releases to the nearest snap point |
open x drag past closeThreshold or flick above velocityThreshold |
Dismisses; shorter, slower drags snap back. | dragging past the threshold or flicking dismisses; short drags snap back |
dismissible=false x long drag; any drag x pointercancel |
Never dismisses; pointercancel resets offsets and emits drag-end with outcome cancel. |
non-dismissible drawers never close from drags and pointercancel restores position |
| tap without movement; click after a drag; drag from form field | Taps keep their click; the click ending a drag is suppressed; inputs never start drags. | clicks survive taps but are suppressed after a drag; form fields never start drags |
dragFromContent=false x drag |
Only drags that start on DrawerHandle move or dismiss the drawer. |
dragFromContent=false limits drags to the handle |
DrawerHandle x click / arrow keys |
Click cycles to the next more-visible snap point with wrap; side-aware arrows step without wrapping. | the handle cycles snap points on click and steps with side-aware arrow keys |
controlled activeSnapPoint x snapTo() |
Emits the request, keeps the parent value, and ignores unknown snap points. | controlled snap points emit requests and follow the parent |
| invalid snap points; parts without root | Throw VIZE_UI_DRAWER_SNAP_POINT and VIZE_UI_CONTEXT_MISSING. |
invalid snap points and missing providers throw stable diagnostics |
| snap helpers | Pixel/fraction offsets, release resolution, and snap stepping are pure and exported. | snap math resolves pixel and fractional points with velocity projection |
| server render closed and open | Byte-identical output, no open attribute, no slot contents while closed, no offset variables. |
renders byte-identical closed drawer markup without opening the native dialog |
| hydration of an open drawer | Reuses the server <dialog>, then calls showModal() and publishes offsets with zero diagnostics. |
renders open drawers deterministically and hydrates before calling showModal |
| public types | Side, snap point, outcome, and dismiss reason are closed unions; invalid props are rejected. | src/families/overlays/drawer/drawer.types.test-d.ts |