Vaul Vue Unstyled Drawer
Vaul Vue is an unstyled, headless drawer component specifically designed for Vue 3 applications, serving as a modern and highly customizable replacement for traditional dialogs and modals, particularly optimized for tablet and mobile devices. It stands at version 0.4.1 and demonstrates an active development cadence with frequent patch releases addressing bug fixes and occasional minor releases introducing new features or significant architectural refactorings. This library is a feature-complete port of Emil Kowalski's original Vaul library, which was built for React, ensuring a mature and well-tested interaction model. Under the hood, Vaul Vue leverages Reka UI's Dialog primitive, providing a robust foundation for accessibility and core interaction logic. Its unstyled nature is a key differentiator, granting developers complete control over the visual presentation to seamlessly integrate with any design system. The headless architecture ensures that essential accessibility features are provided out-of-the-box, without dictating styling. Key features include highly customizable snap points, support for various drag directions, and a comprehensive set of primitives (`DrawerRoot`, `DrawerTrigger`, `DrawerPortal`, `DrawerOverlay`, `DrawerContent`) that enable building bespoke drawer experiences tailored to specific application needs, making it a powerful tool for responsive web design.
Common errors
-
CSS styles are not applying to my Vaul drawer component.
cause The default styles for Vaul Vue need to be explicitly imported as of version 0.4.0.fixAdd `import 'vaul-vue/dist/style.css'` to your main application file or component. -
My custom `vaul-*` data attributes are no longer working or applying styles.
cause In v0.4.0, data attributes were refactored from `[vaul-*]` to `[data-vaul-*]` for improved compliance and CSP compatibility.fixUpdate your HTML and CSS to use `data-vaul-*` attributes instead of `vaul-*` (e.g., `data-vaul-drawer` instead of `vaul-drawer`). -
Clicking on the drawer overlay doesn't close the drawer, or body scrolling is still disabled after closing.
cause Earlier versions (before 0.4.1) had known bugs related to overlay dismissal and body style persistence.fixUpgrade to `vaul-vue@0.4.1` or a newer version to benefit from the fixes. -
TypeError: Cannot read properties of undefined (reading 'setup') or similar errors related to Radix Vue components after upgrading.
cause Version 0.3.0 broke compatibility with Radix Vue components by migrating to Reka UI primitives. Your application might still be trying to use Radix Vue components or expecting their API.fixRemove any lingering Radix Vue imports or usage that were previously tied to Vaul Vue. Ensure you are only using the `vaul-vue` components which now internally rely on Reka UI, or follow Reka UI's documentation for direct primitive usage.
Warnings
- breaking The underlying primitive library for Vaul Vue migrated from Radix Vue to Reka UI. This change occurred in v0.3.0 and may require updating component imports or props if directly interacting with the underlying primitives.
- breaking Data attributes used for styling and interaction changed from `[vaul-*]` to `[data-vaul-*]` for better web standards compliance and CSP header compatibility. This affects direct CSS targeting and any manual interaction with these attributes.
- gotcha As of v0.4.0, default styles are no longer automatically included and must be explicitly imported from `vaul-vue/dist/style.css`. Failing to do so will result in an unstyled and potentially non-functional drawer.
- gotcha Before v0.4.1, there were issues where the `<body>` element's styles (e.g., overflow) might persist after the drawer unmounted, and clicking the overlay might not dismiss the drawer as expected.
Install
-
npm install vaul-vue -
yarn add vaul-vue -
pnpm add vaul-vue
Imports
- { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent }
const { DrawerRoot } = require('vaul-vue')import { DrawerRoot, DrawerTrigger, DrawerPortal, DrawerOverlay, DrawerContent } from 'vaul-vue' - CSS Styles
import 'vaul-vue/dist/style.css'
Quickstart
<script setup lang="ts">
import { DrawerContent, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTrigger } from 'vaul-vue'
import 'vaul-vue/dist/style.css' // Important: import Vaul Vue's default styles
</script>
<template>
<DrawerRoot :snap-points="[0.5, 0.8]" :open="true"> <!-- Added :open for demonstration -->
<DrawerTrigger as-child>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Open Drawer</button>
</DrawerTrigger>
<DrawerPortal>
<!-- Overlay for background dimming when drawer is open -->
<DrawerOverlay class="fixed inset-0 bg-black/40 z-40" />
<DrawerContent class="bg-white flex flex-col rounded-t-[10px] h-full mt-24 fixed bottom-0 left-0 right-0 max-h-[96%] max-w-md mx-auto z-50">
<div class="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-gray-300 my-3" />
<div class="flex-1 p-4 overflow-y-auto">
<h2 class="text-xl font-semibold text-gray-800 mb-2">Welcome to Vaul Vue!</h2>
<p class="text-gray-700 leading-relaxed">
This is an unstyled, headless drawer component for Vue 3. It's designed to be a flexible
and accessible replacement for dialogs and modals on mobile and tablet devices.
You can drag it up or down to adjust its position or dismiss it.
It uses <a href="https://www.reka-ui.com/docs/components/dialog" target="_blank" class="text-blue-600 hover:underline">Reka UI's Dialog primitive</a>
under the hood for robust accessibility.
</p>
<p class="text-gray-700 leading-relaxed mt-4">
Feel free to customize all aspects of its appearance with your own CSS or utility classes.
The `snap-points` prop allows you to define specific heights where the drawer can rest.
</p>
<div class="mt-8">
<h3 class="font-medium text-gray-800 mb-2">Key Features:</h3>
<ul class="list-disc list-inside text-gray-700">
<li>Unstyled & Headless</li>
<li>Mobile-first interaction</li>
<li>Customizable snap points</li>
<li>Uses Reka UI for accessibility</li>
</ul>
</div>
<p class="text-gray-500 text-sm mt-8">Drag this drawer down to close it, or click the overlay.</p>
</div>
<div class="border-t border-gray-200 p-4 flex justify-end">
<button class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600">Close Drawer</button>
</div>
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>