Components

Drawer

Source
A drawer that smoothly slides in & out of the screen.
Install with the CLI
npx @vyui/cli add drawer

Overview

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.

Built on the headless 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:open to control the drawer; update:open is emitted on every open-state change.
  • defaultOpen initializes uncontrolled open state (now honored by the underlying SheetRoot).
  • side picks the edge: top, right, bottom, or left; default bottom.
  • overlay toggles the dimmed backdrop; dismissible controls both backdrop dismissal and drag-to-close.
  • handle shows the drag-handle pill; handleOnly restricts dragging to the handle.
  • snapPoints are 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.
  • keyboardAware lifts the footer above the on-screen keyboard when a field inside it is focused.
  • The #content, #header, #body, and #footer slots receive a close() helper for programmatic dismissal.

Props

PropDefaultType
defaultOpenfalseboolean | undefined

Initial open state when uncontrolled.

defaultSnapIndex0number | undefined

Initial snap index when uncontrolled.

descriptionstring | undefined

Header description. Overridden by the `description` slot.

dismissibletrueboolean | undefined

When `false`, tapping the overlay does not close the drawer.

dragDisabledboolean | undefined

Disable dragging on the underlying SheetContent.

handletrueboolean | undefined

Show the drag-handle pill at the top of the drawer.

handleOnlyfalseboolean | undefined

When `true`, only the `<SheetHandle>` is draggable; the drawer body does not respond to touch drag.

keyboardAwarefalseboolean | undefined

When `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.

keyboardAwareForceAttachtrueboolean | undefined

Forwarded to `KeyboardAwareRoot.forceAttach`. Defaults to `true` so the footer sticks to the keyboard's top edge (chat-style UX).

openboolean | undefined

Controlled open state.

overlaytrueboolean | undefined

Render the dim overlay behind the content.

side"bottom""top" | "bottom" | "left" | "right" | undefined

Side the drawer slides in from.

snapPoints[0.75]number[] | undefined

Snap 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.

titlestring | undefined

Header title. Overridden by the `title` slot.

transitiontrueboolean | undefined

Animate the drawer when opening/closing.

uiPartial<Record<"header" | "content" | "body" | "title" | "description" | "close" | "wrapper" | "footer" | "handle" | "overlay" | "scaffold", ClassNameValue>> | undefined

Emits

EventPayload
update:open[value: boolean]

Slots

SlotBindings
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{} | undefined

Override the title text.

description{} | undefined

Override 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 slotPurpose
overlayFull-screen backdrop.
contentSliding panel and edge dimensions.
handleDrag-handle pill.
scaffoldColumn wrapper for header/body/footer.
headerHeader row and spacing.
wrapperTitle and description wrapper.
bodyScrollable main region.
footerBottom action row.
titleBuilt-in title typography.
descriptionBuilt-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 defaultOpen flag — previously dead because the panel was always treated as controlled — is now honored (see useStandardVModel in @vyui/core).
  • Use <VyDrawer :snap-points="[1]"> for a full-screen sheet, or VyModal for a centered blocking dialog.
  • Modal for a centered blocking dialog.
  • Tray for a fitContent sheet that hugs its content.
  • Sheet is the headless @vyui/core primitive underneath.