Usage
Use aButton or any other component in the default slot of the Drawer.
Then, use the#content
slot to add the content displayed when the Drawer is open.
<template> <UDrawer> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="h-48 m-4" /> </template> </UDrawer></template>
You can also use the#header
,#body
and#footer
slots to customize the Drawer's content.
Title
Use thetitle
prop to set the title of the Drawer's header.
<template> <UDrawer title="Drawer with title"> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #body> <Placeholder class="h-48" /> </template> </UDrawer></template>
Description
Use thedescription
prop to set the description of the Drawer's header.
<template> <UDrawer title="Drawer with description" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit." > <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #body> <Placeholder class="h-48" /> </template> </UDrawer></template>
Direction
Use thedirection
prop to control the direction of the Drawer. Defaults tobottom
.
<template> <UDrawer direction="right"> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="min-w-96 min-h-96 size-full m-4" /> </template> </UDrawer></template>
Inset
Use theinset
prop to inset the Drawer from the edges.
<template> <UDrawer direction="right" inset> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="min-w-96 min-h-96 size-full m-4" /> </template> </UDrawer></template>
Handle
Use thehandle
prop to control whether the Drawer has a handle or not. Defaults totrue
.
<template> <UDrawer :handle="false"> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="h-48 m-4" /> </template> </UDrawer></template>
Overlay
Use theoverlay
prop to control whether the Drawer has an overlay or not. Defaults totrue
.
<template> <UDrawer :overlay="false"> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="h-48 m-4" /> </template> </UDrawer></template>
Scale background
Use theshould-scale-background
prop to scale the background when the Drawer is open, creating a visual depth effect.
<template> <UDrawer should-scale-background> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="h-48 m-4" /> </template> </UDrawer></template>
vaul-drawer-wrapper
directive to a parent element of your app to make this work.<template> <UApp> <div class="bg-(--ui-bg)" vaul-drawer-wrapper> <NuxtLayout> <NuxtPage /> </NuxtLayout> </div> </UApp></template>
export default defineNuxtConfig({ app: { rootAttrs: { 'vaul-drawer-wrapper': '', 'class': 'bg-(--ui-bg)' } }})
Examples
Control open state
You can control the open state by using thedefault-open
prop or thev-model:open
directive.
<script setup lang="ts">const open= ref(false)defineShortcuts({ o: () => (open.value= !open.value)})</script><template> <UDrawer v-model:open="open"> <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #content> <Placeholder class="h-48 m-4" /> </template> </UDrawer></template>
defineShortcuts
, you can toggle the Drawer by pressingO.Prevent closing
Set thedismissible
prop tofalse
to prevent the Drawer from being closed when clicking outside of it or pressing escape.
<script setup lang="ts">const open= ref(false)</script><template> <UDrawer v-model:open="open" :dismissible="false" :ui="{ header: 'flex items-center justify-between' }" > <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #header> <h2 class="text-(--ui-text-highlighted) font-semibold">Drawer non-dismissible</h2> <UButton color="neutral" variant="ghost" icon="i-lucide-x" @click="open = false" /> </template> <template #body> <Placeholder class="h-48" /> </template> </UDrawer></template>
header
slot is used to add a close button which is not done by default.With footer slot
Use the#footer
slot to add content after the Drawer's body.
<script setup lang="ts">const open= ref(false)</script><template> <UDrawer v-model:open="open" title="Drawer with footer" description="This is useful when you want a form in a Drawer." :ui="{ container: 'max-w-xl mx-auto' }" > <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" /> <template #body> <Placeholder class="h-48" /> </template> <template #footer> <UButton label="Submit" color="neutral" class="justify-center" /> <UButton label="Cancel" color="neutral" variant="outline" class="justify-center" @click="open = false" /> </template> </UDrawer></template>
With command palette
You can use aCommandPalette component inside the Drawer's content.
<script setup lang="ts">const searchTerm= ref('')const { data: users, status} = await useFetch('https://jsonplaceholder.typicode.com/users', { key: 'command-palette-users', params: { q: searchTerm}, transform: (data: { id: number, name: string, email: string }[]) => { return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } }))|| [] }, lazy: true})const groups= computed(() => [{ id: 'users', label: searchTerm.value? `Users matching “${searchTerm.value}”...` : 'Users', items: users.value|| [], ignoreFilter: true}])</script><template> <UDrawer :handle="false"> <UButton label="Search users..." color="neutral" variant="subtle" icon="i-lucide-search" /> <template #content> <UCommandPalette v-model:search-term="searchTerm" :loading="status === 'pending'" :groups="groups" placeholder="Search users..." class="h-80" /> </template> </UDrawer></template>
API
Props
Prop | Default | Type |
---|---|---|
as |
|
The element or component this component should render as. |
title |
| |
description |
| |
inset |
|
Whether to inset the drawer from the edges. |
content |
The content of the drawer. | |
overlay |
|
Render an overlay behind the drawer. |
handle |
|
Render a handle on the drawer. |
portal |
|
Render the drawer in a portal. |
dismissible |
|
When |
defaultOpen |
| |
open |
| |
modal |
| |
activeSnapPoint |
| |
closeThreshold |
| |
direction |
|
|
fadeFromIndex |
| |
fixed |
| |
nested |
| |
scrollLockTimeout |
| |
shouldScaleBackground |
| |
snapPoints |
| |
ui |
|
Slots
Slot | Type |
---|---|
default |
|
handle |
|
content |
|
header |
|
title |
|
description |
|
body |
|
footer |
|
Emits
Event | Type |
---|---|
close |
|
drag |
|
update:open |
|
release |
|
update:activeSnapPoint |
|
animationEnd |
|
Theme
export default defineAppConfig({ ui: { drawer: { slots: { overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75', content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none', handle: 'shrink-0 rounded-full bg-(--ui-bg-accented)', container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto', header: '', title: 'text-(--ui-text-highlighted) font-semibold', description: 'mt-1 text-(--ui-text-muted) text-sm', body: 'flex-1', footer: 'flex flex-col gap-1.5' }, variants: { direction: { top: { content: 'mb-24 flex-col-reverse', handle: 'mb-4' }, right: { content: 'flex-row', handle: 'ml-4' }, bottom: { content: 'mt-24 flex-col', handle: 'mt-4' }, left: { content: 'flex-row-reverse', handle: 'mr-4' } }, inset: { true: { content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden' } } }, compoundVariants: [ { direction: [ 'top', 'bottom' ], class: { content: 'h-auto max-h-[96%]', handle: 'w-12 h-1.5 mx-auto' } }, { direction: [ 'right', 'left' ], class: { content: 'w-auto max-w-[calc(100%-2rem)]', handle: 'h-12 w-1.5 mt-auto mb-auto' } }, { direction: 'top', inset: true, class: { content: 'inset-x-4 top-4' } }, { direction: 'top', inset: false, class: { content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]' } }, { direction: 'bottom', inset: true, class: { content: 'inset-x-4 bottom-4' } }, { direction: 'bottom', inset: false, class: { content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]' } }, { direction: 'left', inset: true, class: { content: 'inset-y-4 left-4' } }, { direction: 'left', inset: false, class: { content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]' } }, { direction: 'right', inset: true, class: { content: 'inset-y-4 right-4' } }, { direction: 'right', inset: false, class: { content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]' } } ] } }})
import { defineConfig } from 'vite'import vuefrom '@vitejs/plugin-vue'import uifrom '@nuxt/ui/vite'export default defineConfig({ plugins: [ vue(), ui({ ui: { drawer: { slots: { overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75', content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none', handle: 'shrink-0 rounded-full bg-(--ui-bg-accented)', container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto', header: '', title: 'text-(--ui-text-highlighted) font-semibold', description: 'mt-1 text-(--ui-text-muted) text-sm', body: 'flex-1', footer: 'flex flex-col gap-1.5' }, variants: { direction: { top: { content: 'mb-24 flex-col-reverse', handle: 'mb-4' }, right: { content: 'flex-row', handle: 'ml-4' }, bottom: { content: 'mt-24 flex-col', handle: 'mt-4' }, left: { content: 'flex-row-reverse', handle: 'mr-4' } }, inset: { true: { content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden' } } }, compoundVariants: [ { direction: [ 'top', 'bottom' ], class: { content: 'h-auto max-h-[96%]', handle: 'w-12 h-1.5 mx-auto' } }, { direction: [ 'right', 'left' ], class: { content: 'w-auto max-w-[calc(100%-2rem)]', handle: 'h-12 w-1.5 mt-auto mb-auto' } }, { direction: 'top', inset: true, class: { content: 'inset-x-4 top-4' } }, { direction: 'top', inset: false, class: { content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]' } }, { direction: 'bottom', inset: true, class: { content: 'inset-x-4 bottom-4' } }, { direction: 'bottom', inset: false, class: { content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]' } }, { direction: 'left', inset: true, class: { content: 'inset-y-4 left-4' } }, { direction: 'left', inset: false, class: { content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]' } }, { direction: 'right', inset: true, class: { content: 'inset-y-4 right-4' } }, { direction: 'right', inset: false, class: { content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]' } } ] } } }) ]})
import { defineConfig } from 'vite'import vuefrom '@vitejs/plugin-vue'import uiProfrom '@nuxt/ui-pro/vite'export default defineConfig({ plugins: [ vue(), uiPro({ ui: { drawer: { slots: { overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75', content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none', handle: 'shrink-0 rounded-full bg-(--ui-bg-accented)', container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto', header: '', title: 'text-(--ui-text-highlighted) font-semibold', description: 'mt-1 text-(--ui-text-muted) text-sm', body: 'flex-1', footer: 'flex flex-col gap-1.5' }, variants: { direction: { top: { content: 'mb-24 flex-col-reverse', handle: 'mb-4' }, right: { content: 'flex-row', handle: 'ml-4' }, bottom: { content: 'mt-24 flex-col', handle: 'mt-4' }, left: { content: 'flex-row-reverse', handle: 'mr-4' } }, inset: { true: { content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden' } } }, compoundVariants: [ { direction: [ 'top', 'bottom' ], class: { content: 'h-auto max-h-[96%]', handle: 'w-12 h-1.5 mx-auto' } }, { direction: [ 'right', 'left' ], class: { content: 'w-auto max-w-[calc(100%-2rem)]', handle: 'h-12 w-1.5 mt-auto mb-auto' } }, { direction: 'top', inset: true, class: { content: 'inset-x-4 top-4' } }, { direction: 'top', inset: false, class: { content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]' } }, { direction: 'bottom', inset: true, class: { content: 'inset-x-4 bottom-4' } }, { direction: 'bottom', inset: false, class: { content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]' } }, { direction: 'left', inset: true, class: { content: 'inset-y-4 left-4' } }, { direction: 'left', inset: false, class: { content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]' } }, { direction: 'right', inset: true, class: { content: 'inset-y-4 right-4' } }, { direction: 'right', inset: false, class: { content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]' } } ] } } }) ]})