Drawer
npx @vyui/cli add drawerOverview
VyDrawer presents a panel that slides in from any viewport edge and settles at a snap point. It supports drag-to-dismiss, a drag handle, an optional backdrop, multiple snap points, keyboard-aware footers, and controlled or uncontrolled open state. The default slot carries the trigger, then #content / #header / #body / #footer slots shape the panel.
Sheet primitive from @vyui/core. Drop down to it when you need full control over the snap physics and markup.Usage
Use a VyButton — or any component — in the default slot of the VyDrawer. Then use the #content slot to add the content displayed when the drawer opens.
The default slot is wrapped in the core SheetTrigger; tapping it opens the drawer. Do not also set the open state from a tap handler on the slotted child.
Use the #header, #body and #footer slots to customize the drawer's content. title and description populate the built-in header.
<VyDrawer
v-model:open="open"
title="Edit profile"
description="Make changes to your profile here."
>
<VyButton label="Open drawer" />
<template #body>
<view class="flex flex-col gap-3 p-4">
<!-- drawer body -->
</view>
</template>
<template #footer="{ close }">
<VyButton color="neutral" variant="ghost" label="Cancel" @tap="close" />
<VyButton label="Save" @tap="close" />
</template>
</VyDrawer>
Directions
Use side to control which edge the drawer slides in from. It defaults to bottom.
Controlling open state
Bind v-model:open to control the drawer. When uncontrolled, defaultOpen seeds the initial state.
<script setup lang="ts">
const open = ref(false)
</script>
<template>
<VyDrawer v-model:open="open" title="Notifications">
<VyButton label="Open" />
<template #body>
<Placeholder class="h-48 m-4" />
</template>
</VyDrawer>
</template>
Features and behavior
- Bind
v-model:opento control the drawer;update:openis emitted on every open-state change. defaultOpeninitializes uncontrolled open state (now honored by the underlyingSheetRoot).sidepicks the edge:top,right,bottom, orleft; defaultbottom.overlaytoggles the dimmed backdrop;dismissiblecontrols both backdrop dismissal and drag-to-close.handleshows the drag-handle pill;handleOnlyrestricts dragging to the handle.snapPointsare 0 → 1 fractions of the viewport on the slide axis. Defaults to[0.75](a three-quarter-height bottom sheet). Pass[1]for full-screen or e.g.[0.4, 0.9]for a resizable sheet.keyboardAwarelifts the footer above the on-screen keyboard when a field inside it is focused.- The
#content,#header,#body, and#footerslots receive aclose()helper for programmatic dismissal.
Props
| Prop | Default | Type |
|---|---|---|
defaultOpen | false | boolean | undefinedInitial open state when uncontrolled. |
defaultSnapIndex | 0 | number | undefinedInitial snap index when uncontrolled. |
description | — | string | undefinedHeader description. Overridden by the `description` slot. |
dismissible | true | boolean | undefinedWhen `false`, tapping the overlay does not close the drawer. |
dragDisabled | — | boolean | undefinedDisable dragging on the underlying SheetContent. |
handle | true | boolean | undefinedShow the drag-handle pill at the top of the drawer. |
handleOnly | false | boolean | undefinedWhen `true`, only the `<SheetHandle>` is draggable; the drawer body does not respond to touch drag. |
keyboardAware | false | boolean | undefinedWhen `true`, the footer translates upward to stay above the on-screen keyboard on Lynx: the scaffold is wrapped in `KeyboardAwareRoot`, the footer in `KeyboardAwareResponder`/`Trigger`, so any focused input inside the footer drives the lift. No-op on web/jsdom. |
keyboardAwareForceAttach | true | boolean | undefinedForwarded to `KeyboardAwareRoot.forceAttach`. Defaults to `true` so the footer sticks to the keyboard's top edge (chat-style UX). |
open | — | boolean | undefinedControlled open state. |
overlay | true | boolean | undefinedRender the dim overlay behind the content. |
side | "bottom" | "top" | "bottom" | "left" | "right" | undefinedSide the drawer slides in from. |
snapPoints | [0.75] | number[] | undefinedSnap fractions (0 → 1) forwarded to `SheetRoot`. Defaults to a single three-quarter snap so a bottom drawer doesn't take over the viewport; pass `[1]` for full-screen or e.g. `[0.4, 0.9]` for a resizable sheet. |
title | — | string | undefinedHeader title. Overridden by the `title` slot. |
transition | true | boolean | undefinedAnimate the drawer when opening/closing. |
ui | — | Partial<Record<"header" | "content" | "body" | "title" | "description" | "close" | "wrapper" | "footer" | "handle" | "overlay" | "scaffold", ClassNameValue>> | undefined |
Emits
| Event | Payload |
|---|---|
update:open | [value: boolean] |
Slots
| Slot | Bindings |
|---|---|
default | { open: boolean; }Trigger content. `SheetTrigger` sets open to `true` on tap; do NOT also bind a `@tap` handler that sets open — use `v-model:open`. |
content | { close: () => void; }Full content override — replaces the default header/body/footer layout. |
header | { close: () => void; }Header region. |
title | {} | undefinedOverride the title text. |
description | {} | undefinedOverride the description text. |
body | { close: () => void; }Main body content. |
footer | { close: () => void; }Footer region. |
Styling and theming
Override globally through appConfig.ui.drawer or locally through ui.
| UI slot | Purpose |
|---|---|
overlay | Full-screen backdrop. |
content | Sliding panel and edge dimensions. |
handle | Drag-handle pill. |
scaffold | Column wrapper for header/body/footer. |
header | Header row and spacing. |
wrapper | Title and description wrapper. |
body | Scrollable main region. |
footer | Bottom action row. |
title | Built-in title typography. |
description | Built-in supporting text. |
close | (Reserved) built-in close-button positioning. |
Variants are side (edge placement and dimensions) and transition. Open/close motion is driven by core's Presence keyframes, so the slide stays native-smooth.
Accessibility
The trigger exposes native button semantics and announces collapsed or expanded state. Provide an accessibility-label on icon-only triggers.
Lynx has no DOM focus trap and hardware Escape handling is not currently wired. Test focus order, background isolation, and dismissal with VoiceOver and TalkBack on each target platform.
Platform notes
- Drag tracking and snap motion run on the main thread (MTS) for native-smooth tracking.
- The
defaultOpenflag — previously dead because the panel was always treated as controlled — is now honored (seeuseStandardVModelin@vyui/core). - Use
<VyDrawer :snap-points="[1]">for a full-screen sheet, orVyModalfor a centered blocking dialog.