Usage
Use a Button 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 the title
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 the description
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 the direction
prop to control the direction of the Drawer. Defaults to bottom
.
<template>
<UDrawer direction="top">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-96 m-4" />
</template>
</UDrawer>
</template>
<template>
<UDrawer direction="right">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="w-96 m-4" />
</template>
</UDrawer>
</template>
Handle
Use the handle
prop to control whether the Drawer has a handle or not. Defaults to true
.
<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 the overlay
prop to control whether the Drawer has an overlay or not. Defaults to true
.
<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 the should-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-[var(--ui-bg)]" vaul-drawer-wrapper>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</UApp>
</template>
export default defineNuxtConfig({
app: {
rootAttrs: {
'vaul-drawer-wrapper': '',
'class': 'bg-[var(--ui-bg)]'
}
}
})
Examples
Control open state
You can control the open state by using the default-open
prop or the v-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>
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 a CommandPalette component inside the Drawer's content.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/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 || [],
filter: false
}])
</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 |
| |
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. |
open |
| |
defaultOpen |
| |
fixed |
| |
modal |
| |
activeSnapPoint |
| |
closeThreshold |
| |
direction |
|
|
dismissible |
| |
fadeFromIndex |
| |
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 |
|
Theme
export default defineAppConfig({
ui: {
drawer: {
slots: {
overlay: 'fixed inset-0 bg-[var(--ui-bg-elevated)]/75',
content: 'fixed bg-[var(--ui-bg)] ring ring-[var(--ui-border)] flex focus:outline-none',
handle: 'shrink-0 rounded-full bg-[var(--ui-bg-accented)]',
container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
header: '',
title: 'text-[var(--ui-text-highlighted)] font-semibold',
description: 'mt-1 text-[var(--ui-text-muted)] text-sm',
body: 'flex-1',
footer: 'flex flex-col gap-1.5'
},
variants: {
direction: {
top: {
content: 'top-0 mb-24 flex-col-reverse rounded-b-[calc(var(--ui-radius)*2)]',
handle: 'mb-4'
},
right: {
content: 'right-4 flex-row',
handle: 'ml-4'
},
bottom: {
content: 'bottom-0 mt-24 flex-col rounded-t-[calc(var(--ui-radius)*2)]',
handle: 'mt-4'
},
left: {
content: 'left-4 flex-row-reverse',
handle: 'mr-4'
}
}
},
compoundVariants: [
{
direction: [
'top',
'bottom'
],
class: {
content: 'inset-x-0 h-auto max-h-[96%]',
handle: 'w-12 h-1.5 mx-auto'
}
},
{
direction: [
'right',
'left'
],
class: {
content: 'inset-y-4 w-auto max-w-[calc(100%-2rem)] rounded-[calc(var(--ui-radius)*2)] after:hidden',
handle: 'h-12 w-1.5 mt-auto mb-auto'
}
}
]
}
}
})