vue-bottom-sheets

Accessible, draggable bottom sheet component for Vue 3 with snap points — inspired by react-spring-bottom-sheet.
2,773
Install
npm install vue-bottom-sheets
Latest Version:0.4.0
Vue:3
License:GPL-3.0-or-later
Last Updated:Jun 10, 2026
Links: npm
Maintainer: psergienko

vue-bottom-sheets

Accessible, draggable bottom sheet component for Vue 3 with snap points — inspired by react-spring-bottom-sheet.

Installation

npm install vue-bottom-sheets
# or: pnpm add vue-bottom-sheets

Usage

<script setup lang="ts">
import { ref } from 'vue'
import { BottomSheet } from 'vue-bottom-sheet'

const open = ref(false)
</script>

<template>
  <button @click="open = true">Open</button>

  <BottomSheet v-model:open="open" aria-label="Example sheet">
    <template #header>
      <h2>Title</h2>
    </template>

    <p>Sheet content goes here.</p>

    <template #footer>
      <button @click="open = false">Close</button>
    </template>
  </BottomSheet>
</template>

Global registration (plugin)

import { createApp } from 'vue'
import VueBottomSheet from 'vue-bottom-sheets'

createApp(App).use(VueBottomSheet) // registers <BottomSheet> globally

Snap points

Provide a fixed array of pixel heights, or a function that computes them from the measured geometry:

<BottomSheet
  v-model:open="open"
  :snap-points="({ maxHeight, minHeight }) => [Math.min(minHeight, 120), maxHeight * 0.5, maxHeight - 40]"
  :default-snap="({ snapPoints }) => snapPoints[1]"
/>

The resolver receives { height, maxHeight, headerHeight, footerHeight, minHeight } (all in pixels).

Imperative API

<script setup lang="ts">
import { ref } from 'vue'
import { BottomSheet } from 'vue-bottom-sheet'

const sheet = ref<InstanceType<typeof BottomSheet> | null>(null)

// Jump to the tallest snap point.
function expand() {
  sheet.value?.snapTo(({ snapPoints }) => snapPoints[snapPoints.length - 1])
}
</script>

<template>
  <BottomSheet ref="sheet" v-model:open="open">…</BottomSheet>
</template>

Props

Prop Type Default Description
open boolean false Controlled open state. Use with v-model:open.
snapPoints number[] | (props) => number[] content size Heights (px) the sheet can rest at.
defaultSnap number | (props) => number largest Snap point used when opening.
blocking boolean true Render backdrop, trap focus and lock scroll.
closeOnBackdropClick boolean true Dismiss when the backdrop is clicked.
closeOnEscape boolean true Dismiss when Escape is pressed.
scrollLocking boolean true Lock body scroll while open and blocking.
expandOnContentDrag boolean false Allow dragging the sheet from its content area.
handle boolean true Render the built-in handle pill (and header strip). Set false to use your own handle.
maxHeight number viewport Hard cap on sheet height (px).
ariaLabel string — Accessible label applied as aria-label.
skipInitialTransition boolean false Skip the open animation on first mount.
theme BottomSheetTheme — Visual overrides (colors, radius, shadow, …). See Theming.

Events

Event Payload Description
update:open boolean Emitted for v-model:open.
dismiss — User requested dismissal (backdrop/escape/drag).
snap number Sheet settled on a snap point (px).
spring-start — A transition started.
spring-end — A transition finished.

Slots

Slot Slot props Description
default dragHandleProps Scrollable body content.
header dragHandleProps Sticky header above the content; the drag handle.
footer — Sticky footer below the content (rendered if present).

Custom drag handle

By default the sheet renders its own handle strip. To make a consumer-supplied element the grab region instead — for example a header that already lives inside your content — set :handle="false" and spread the dragHandleProps slot prop onto that element:

<BottomSheet v-model:open="open" :handle="false">
  <template #default="{ dragHandleProps }">
    <!-- Your own header IS the drag handle; the ✕ still clicks. -->
    <header class="my-header" v-bind="dragHandleProps">
      <h2>Title</h2>
      <button @click="open = false">✕</button>
    </header>

    <p>Scrollable body…</p>
  </template>
</BottomSheet>

dragHandleProps wires up the pointer listeners and applies touch-action: none so the browser won't claim the touch for scrolling. A drag only begins once the pointer moves past a small threshold, so taps/clicks on buttons inside the handle still fire. The slot prop is also available on #header.

Theming

Pass a theme object — each key is optional and falls back to the default. Numeric keys (maxWidth, radius, transitionDuration, zIndex) also accept a plain number (px, px, ms, unitless):

<BottomSheet
  v-model:open="open"
  :theme="{
    radius: 24,
    background: '#111',
    color: '#fff',
    backdrop: 'rgba(0, 0, 0, 0.6)',
    handle: 'rgba(255, 255, 255, 0.3)',
  }"
/>
Key Type CSS variable Default
maxWidth string | number --vbs-max-width 640px
radius string | number --vbs-radius 16px
background string --vbs-bg #ffffff
color string --vbs-color #1a1a1a
backdrop string --vbs-backdrop rgba(0, 0, 0, 0.45)
handle string --vbs-handle rgba(0, 0, 0, 0.18)
shadow string --vbs-shadow 0 -8px 40px rgba(0, 0, 0, 0.18)
transitionDuration string | number --vbs-transition-duration 320ms
transitionEasing string --vbs-transition-easing cubic-bezier(0.22, 1, 0.36, 1)
zIndex string | number --vbs-z-index 1000

Prefer CSS? The same --vbs-* custom properties can be set on .vbs (or any ancestor) instead of using the prop. prefers-reduced-motion is respected automatically.

Development

pnpm install
pnpm dev          # run the playground at http://localhost:5173
pnpm test         # run unit tests
pnpm build        # type-check + build the library to dist/
pnpm lint         # lint & autofix

License

GPL-3.0-or-later © Pavel Sergienko

Related Packages

@guillemservera/vue-drawer

A headless, unstyled and behaviorally opinionated drawer primitive for Vue 3 wit...

2,226 0
bottom-sheet-vue3

A touch-supported bottom sheet library for Vue 3

78,845 31
vue-native-bottom-sheet

Production-ready bottom sheet for Vue 3 with native iOS/Android-like drag physic...

0 3
vue-vaul

A drawer component for Vue

9,592 0

Version History

Version Released Vue License
0.4.0 ^3.3.0 GPL-3.0-or-later
0.3.0 ^3.3.0 GPL-3.0-or-later
0.2.0 ^3.3.0 GPL-3.0-or-later
0.1.1 ^3.3.0 GPL-3.0-or-later
0.1.0 ^3.3.0 GPL-3.0-or-later