Vize

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