Gebruik
Gebruik een Button of een ander onderdeel in de standaardsleuf van de lade.
Gebruik vervolgens de #content-sleuf om de inhoud toe te voegen die wordt weergegeven wanneer de lade is geopend.
<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>U kunt ook de slots #header, #body en #footer gebruiken om de inhoud van de lade aan te passen.
Titel
Gebruik de title prop om de titel van de koptekst van de lade in te stellen.
<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>Beschrijving
Gebruik de description prop om de beschrijving van de koptekst van de lade in te stellen.
<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>Sluiten: badge{label="4.10+" class="align-text-top"}
Gebruik de close prop om een sluitknop in de lade weer te geven. Standaard false.
U kunt elke eigenschap van de Button component doorgeven om deze aan te passen.
<template>
<UDrawer
title="Drawer with close button"
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Sluiten Icoon: badge{label="4.10+" class="align-text-top"}
Gebruik de close-icon prop om de knop Sluiten aan te passen Icon. Standaard i-lucide-x.
<template>
<UDrawer title="Drawer with close button" close close-icon="i-lucide-arrow-right">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="h-48" />
</template>
</UDrawer>
</template>Richting
Gebruik de direction prop om de richting van de lade te regelen. Standaard bottom.
<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>Inzet
Gebruik de inset prop om de lade vanaf de randen in te zetten.
<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>Handvat
Gebruik de handle prop om te bepalen of de lade een handvat heeft of niet. Standaard 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>Alleen handvat
Gebruik de handle-only prop om alleen de lade door het handvat te laten slepen.
<template>
<UDrawer handle-only>
<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
Gebruik de overlay prop om te bepalen of de lade een overlay heeft of niet. Standaard 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>Modaal
Gebruik de modal prop om te bepalen of de lade de interactie met externe inhoud blokkeert. Standaard true.
modal is ingesteld op false, wordt de overlay automatisch uitgeschakeld en wordt externe inhoud interactief.<template>
<UDrawer :modal="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>Ontvankelijk
Gebruik de dismissible-prop om te bepalen of de lade kan worden afgewezen wanneer u erbuiten klikt of op escape drukt. Standaard true.
close:prevent-gebeurtenis wordt uitgezonden wanneer de gebruiker deze probeert te sluiten.modal: false combineren met dismissible: false om de achtergrond van de lade interactief te maken zonder deze te sluiten.<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer non-dismissible"
close
:dismissible="false"
:modal="false"
:handle="false"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="size-full min-h-48" />
</template>
</UDrawer>
</template><script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<UDrawer
v-model:open="open"
title="Drawer non-dismissible"
close
:dismissible="false"
:modal="false"
:handle="false"
>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #body>
<Placeholder class="size-full min-h-48" />
</template>
</UDrawer>
</template>Schaal Achtergrond
Gebruik de should-scale-background prop om de achtergrond te schalen wanneer de lade open is, waardoor een visueel diepteeffect ontstaat.
U kunt de set-background-color-on-scale prop instellen op false om te voorkomen dat de achtergrondkleur verandert.
<template>
<UDrawer should-scale-background set-background-color-on-scale>
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UDrawer>
</template>data-vaul-drawer-wrapper-richtlijn toevoegt aan een bovenliggend element van uw app om dit te laten werken.<template>
<UApp>
<div class="bg-default" data-vaul-drawer-wrapper>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</UApp>
</template>
export default defineNuxtConfig({
app: {
rootAttrs: {
'data-vaul-drawer-wrapper': '',
'class': 'bg-default'
}
}
})
Voorbeelden
Control open staat
U kunt de open status regelen met behulp van de default-open prop of de v-model:open richtlijn.
<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><script setup lang="ts">
import { ref } from 'vue'
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, kunt u de lade omschakelen door op: kbd{value="O"} te drukken.Responsieve lade
U kunt bijvoorbeeld een Modal component op desktop en een lade op mobiel renderen.
<script lang="ts" setup>
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
const [DefineFormTemplate, ReuseFormTemplate] = createReusableTemplate()
const isDesktop = useMediaQuery('(min-width: 768px)')
const open = ref(false)
const state = reactive({
email: undefined
})
const title = 'Edit profile'
const description = "Make changes to your profile here. Click save when you're done."
</script>
<template>
<DefineFormTemplate>
<UForm :state="state" class="space-y-4">
<UFormField label="Email" name="email" required>
<UInput v-model="state.email" placeholder="shadcn@example.com" required />
</UFormField>
<UButton label="Save changes" type="submit" />
</UForm>
</DefineFormTemplate>
<UModal v-if="isDesktop" v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UModal>
<UDrawer v-else v-model:open="open" :title="title" :description="description">
<UButton label="Edit profile" color="neutral" variant="outline" />
<template #body>
<ReuseFormTemplate />
</template>
</UDrawer>
</template>Genestelde laden
U kunt laden in elkaar nestelen door de nested prop te gebruiken.
<template>
<UDrawer :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
<template #footer>
<UDrawer nested :ui="{ content: 'h-full', overlay: 'bg-inverted/30' }">
<UButton color="neutral" variant="outline" label="Open nested" />
<template #content>
<Placeholder class="flex-1 m-4" />
</template>
</UDrawer>
</template>
</UDrawer>
</template>Met voetgangerssleuf
Gebruik de #footer-sleuf om inhoud toe te voegen na het lichaam van de lade.
<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><script setup lang="ts">
import { ref } from 'vue'
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>Met opdrachtpalet
U kunt een CommandPalette -component gebruiken in de inhoud van de lade.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'drawer-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}`, loading: 'lazy' as const } })) || []
},
immediate: false
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
function onOpen() {
if (!users.value?.length) {
execute()
}
}
</script>
<template>
<UDrawer :handle="false" @update:open="onOpen">
<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>
<script setup lang="ts">
import { ref, computed } from 'vue'
const searchTerm = ref('')
const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'drawer-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}`, loading: 'lazy' as const } })) || []
},
immediate: false
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
function onOpen() {
if (!users.value?.length) {
execute()
}
}
</script>
<template>
<UDrawer :handle="false" @update:open="onOpen">
<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>
useLazyFetch met immediate: false om alleen gegevens op te halen wanneer de lade wordt geopend.API
Props
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
title | string | |
description | string | |
inset | false | boolean Whether to inset the drawer from the edges. |
content | DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>The content of the drawer. | |
overlay | true | boolean Render an overlay behind the drawer. |
handle | true | boolean Render a handle on the drawer. |
portal | true | string | false | true | HTMLElementRender the drawer in a portal. |
nested | false | boolean Whether the drawer is nested in another drawer. |
close | false | boolean | Omit<ButtonProps, LinkPropsKeys> Display a close button to dismiss the drawer.
|
closeIcon | appConfig.ui.icons.close | anyThe icon displayed in the close button. |
activeSnapPoint | null | string | number | |
closeThreshold | numberNumber between 0 and 1 that determines when the drawer should be closed. Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more. | |
shouldScaleBackground | boolean | |
setBackgroundColorOnScale | boolean When | |
scrollLockTimeout | numberDuration for which the drawer is not draggable after scrolling content inside of the drawer. | |
fixed | boolean When | |
dismissible | true | boolean When |
modal | true | boolean When |
open | boolean | |
defaultOpen | boolean Opened by default, skips initial enter animation. Still reacts to | |
direction | 'bottom' | "bottom" | "top" | "left" | "right"Direction of the drawer. Can be |
noBodyStyles | boolean When | |
handleOnly | boolean When | |
preventScrollRestoration | boolean | |
snapPoints | (string | number)[]Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up.
Should go from least visible. Example | |
ui | { overlay?: SlotClass; content?: SlotClass; handle?: SlotClass; container?: SlotClass; header?: SlotClass; wrapper?: SlotClass; title?: SlotClass; description?: SlotClass; actions?: SlotClass; body?: SlotClass; footer?: SlotClass; close?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
default | {} |
content | {} |
header | {} |
title | {} |
description | {} |
actions | {} |
close | { ui: object; } |
body | {} |
footer | {} |
Uitzendt
| Event | Type |
|---|---|
close | [] |
drag | [percentageDragged: number] |
close:prevent | [] |
release | [open: boolean] |
update:open | [open: boolean] |
update:activeSnapPoint | [val: string | number] |
animationEnd | [open: boolean] |
Thema
export default defineAppConfig({
ui: {
drawer: {
slots: {
overlay: 'fixed inset-0 bg-elevated/75',
content: 'fixed bg-default ring ring-default flex focus:outline-none',
handle: [
'shrink-0 !bg-accented',
'transition-opacity ease-out'
],
container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
header: 'flex items-center gap-1.5 min-h-8',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
actions: 'flex items-center gap-1.5 shrink-0 ms-auto',
body: 'flex-1',
footer: 'flex flex-col gap-1.5',
close: ''
},
variants: {
direction: {
top: {
content: 'mb-24 flex-col-reverse',
handle: 'mb-4'
},
right: {
content: 'flex-row rtl:flex-row-reverse',
handle: '!ml-4'
},
bottom: {
content: 'mt-24 flex-col',
handle: 'mt-4'
},
left: {
content: 'flex-row-reverse rtl:flex-row',
handle: '!mr-4'
}
},
inset: {
true: {
content: 'rounded-lg after:hidden overflow-hidden [--initial-transform:calc(100%+1.5rem)]'
}
},
snapPoints: {
true: ''
}
},
compoundVariants: [
{
direction: [
'top',
'bottom'
],
class: {
content: 'h-auto max-h-[96%]',
handle: '!w-12 !h-1.5 mx-auto'
}
},
{
direction: [
'top',
'bottom'
],
snapPoints: true,
class: {
content: 'h-full'
}
},
{
direction: [
'right',
'left'
],
class: {
content: 'w-auto max-w-[calc(100%-2rem)]',
handle: '!h-12 !w-1.5 mt-auto mb-auto'
}
},
{
direction: [
'right',
'left'
],
snapPoints: true,
class: {
content: 'w-full'
}
},
{
direction: 'top',
inset: true,
class: {
content: 'inset-x-4 top-4'
}
},
{
direction: 'top',
inset: false,
class: {
content: 'inset-x-0 top-0 rounded-b-lg'
}
},
{
direction: 'bottom',
inset: true,
class: {
content: 'inset-x-4 bottom-4'
}
},
{
direction: 'bottom',
inset: false,
class: {
content: 'inset-x-0 bottom-0 rounded-t-lg'
}
},
{
direction: 'left',
inset: true,
class: {
content: 'inset-y-4 left-4'
}
},
{
direction: 'left',
inset: false,
class: {
content: 'inset-y-0 left-0 rounded-r-lg'
}
},
{
direction: 'right',
inset: true,
class: {
content: 'inset-y-4 right-4'
}
},
{
direction: 'right',
inset: false,
class: {
content: 'inset-y-0 right-0 rounded-l-lg'
}
}
]
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
drawer: {
slots: {
overlay: 'fixed inset-0 bg-elevated/75',
content: 'fixed bg-default ring ring-default flex focus:outline-none',
handle: [
'shrink-0 !bg-accented',
'transition-opacity ease-out'
],
container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
header: 'flex items-center gap-1.5 min-h-8',
wrapper: 'min-w-0 flex-1',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
actions: 'flex items-center gap-1.5 shrink-0 ms-auto',
body: 'flex-1',
footer: 'flex flex-col gap-1.5',
close: ''
},
variants: {
direction: {
top: {
content: 'mb-24 flex-col-reverse',
handle: 'mb-4'
},
right: {
content: 'flex-row rtl:flex-row-reverse',
handle: '!ml-4'
},
bottom: {
content: 'mt-24 flex-col',
handle: 'mt-4'
},
left: {
content: 'flex-row-reverse rtl:flex-row',
handle: '!mr-4'
}
},
inset: {
true: {
content: 'rounded-lg after:hidden overflow-hidden [--initial-transform:calc(100%+1.5rem)]'
}
},
snapPoints: {
true: ''
}
},
compoundVariants: [
{
direction: [
'top',
'bottom'
],
class: {
content: 'h-auto max-h-[96%]',
handle: '!w-12 !h-1.5 mx-auto'
}
},
{
direction: [
'top',
'bottom'
],
snapPoints: true,
class: {
content: 'h-full'
}
},
{
direction: [
'right',
'left'
],
class: {
content: 'w-auto max-w-[calc(100%-2rem)]',
handle: '!h-12 !w-1.5 mt-auto mb-auto'
}
},
{
direction: [
'right',
'left'
],
snapPoints: true,
class: {
content: 'w-full'
}
},
{
direction: 'top',
inset: true,
class: {
content: 'inset-x-4 top-4'
}
},
{
direction: 'top',
inset: false,
class: {
content: 'inset-x-0 top-0 rounded-b-lg'
}
},
{
direction: 'bottom',
inset: true,
class: {
content: 'inset-x-4 bottom-4'
}
},
{
direction: 'bottom',
inset: false,
class: {
content: 'inset-x-0 bottom-0 rounded-t-lg'
}
},
{
direction: 'left',
inset: true,
class: {
content: 'inset-y-4 left-4'
}
},
{
direction: 'left',
inset: false,
class: {
content: 'inset-y-0 left-0 rounded-r-lg'
}
},
{
direction: 'right',
inset: true,
class: {
content: 'inset-y-4 right-4'
}
},
{
direction: 'right',
inset: false,
class: {
content: 'inset-y-0 right-0 rounded-l-lg'
}
}
]
}
}
})
]
})