Gebruik
Gebruik een Button of een ander onderdeel in de standaardsleuf van de modus.
Gebruik vervolgens de #content-sleuf om de inhoud toe te voegen die wordt weergegeven wanneer de modus is geopend.
<template>
<UModal>
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>U kunt ook de #header, #body en #footer slots gebruiken om de inhoud van de Modal aan te passen.
Titel
Gebruik de title prop om de titel van de header van de Modal in te stellen.
<template>
<UModal title="Modal with title">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Beschrijving
Gebruik de description prop om de beschrijving van de header van de Modal in te stellen.
<template>
<UModal
title="Modal with description"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
>
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Sluiten
Gebruik de close prop om de sluitknop (met false-waarde) die wordt weergegeven in de kop van de modus aan te passen of te verbergen.
U kunt elke eigenschap van de Button component doorgeven om deze aan te passen.
<template>
<UModal
title="Modal with close button"
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}"
>
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>#content-sleuf wordt gebruikt omdat deze deel uitmaakt van de header.Sluit pictogram
Gebruik de close-icon prop om de knop Sluiten aan te passen Icon. Standaard i-lucide-x.
<template>
<UModal title="Modal with close button" close-icon="i-lucide-arrow-right">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Overgang
Gebruik de transition-prop om te bepalen of de modus geanimeerd is of niet. Standaard true.
<template>
<UModal :transition="false" title="Modal without transition">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Overlay
Gebruik de overlay prop om te bepalen of de Modal een overlay heeft of niet. Standaard true.
<template>
<UModal :overlay="false" title="Modal without overlay">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Modaal
Gebruik de modal prop om te bepalen of de Modal de interactie met externe inhoud blokkeert. Standaard true.
modal is ingesteld op false, wordt de overlay automatisch uitgeschakeld en wordt externe inhoud interactief.<template>
<UModal :modal="false" title="Modal interactive">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Ontvankelijk
Gebruik de dismissible-prop om te bepalen of de modus 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 Modal interactief te maken zonder deze te sluiten.<template>
<UModal :dismissible="false" title="Modal non-dismissible">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Doorschuifbaar: badge{label="4.2+" class="align-text-top"}
Gebruik de scrollable prop om de inhoud van de Modal binnen de overlay te laten scrollen.
modal: false niet compatibel en verwijdert overlay: false alleen de achtergrond.<template>
<UModal scrollable title="Modal scrollable">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>Volledig scherm
Gebruik de fullscreen prop om de Modal volledig scherm te maken.
<template>
<UModal fullscreen title="Modal fullscreen">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>Ontkoppelen: badge{label="4.10+" class="align-text-top"}
Gebruik de unmount-on-hide prop om te voorkomen dat de inhoud van de Modal wordt verwijderd wanneer deze wordt gesloten. Standaard true.
<template>
<UModal :unmount-on-hide="false" title="Modal">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>portal prop is ingesteld op false, wordt de inhoud ook weergegeven op de server.
Dit is handig om een open Modal tijdens SSR weer te geven zonder een flash bij het laden van de pagina, of om de inhoud ervan bloot te leggen voor SEO.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>
<UModal v-model:open="open">
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<UModal v-model:open="open">
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>
defineShortcuts, kunt u de modus wijzigen door op: kbd{value="O"} te drukken.Programmatisch gebruik
U kunt de useOverlay composable gebruiken om een Modal programmatisch te openen.
App -component die de OverlayProvider -component gebruikt.Maak eerst een modale component die programmatisch wordt geopend:
<script setup lang="ts">
defineProps<{
count: number
}>()
const emit = defineEmits<{ close: [boolean] }>()
</script>
<template>
<UModal
:close="{ onClick: () => emit('close', false) }"
:title="`This modal was opened programmatically ${count} times`"
>
<template #footer>
<div class="flex gap-2">
<UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
<UButton label="Success" @click="emit('close', true)" />
</div>
</template>
</UModal>
</template>close-gebeurtenis uit wanneer de modal hier wordt gesloten of afgewezen.
U kunt alle gegevens uitzenden via de close-gebeurtenis en die gegevens worden de opgeloste waarde van open(). De gebeurtenis moet worden uitgezonden om de belofte op te lossen.Gebruik het vervolgens in uw app:
<script setup lang="ts">
import { LazyModalExample } from '#components'
const count = ref(0)
const toast = useToast()
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)
async function open() {
const instance = modal.open({
count: count.value
})
const shouldIncrement = await instance.result
if (shouldIncrement) {
count.value++
toast.add({
title: `Success: ${shouldIncrement}`,
color: 'success',
id: 'modal-success'
})
// Update the count
modal.patch({
count: count.value
})
return
}
toast.add({
title: `Dismissed: ${shouldIncrement}`,
color: 'error',
id: 'modal-dismiss'
})
}
</script>
<template>
<UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { LazyModalExample } from '#components'
const count = ref(0)
const toast = useToast()
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)
async function open() {
const instance = modal.open({
count: count.value
})
const shouldIncrement = await instance.result
if (shouldIncrement) {
count.value++
toast.add({
title: `Success: ${shouldIncrement}`,
color: 'success',
id: 'modal-success'
})
// Update the count
modal.patch({
count: count.value
})
return
}
toast.add({
title: `Dismissed: ${shouldIncrement}`,
color: 'error',
id: 'modal-dismiss'
})
}
</script>
<template>
<UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
emit('close') uit te zenden.Genestelde modals
Je kunt modalen in elkaar nestelen.
<script setup lang="ts">
const first = ref(false)
const second = ref(false)
</script>
<template>
<UModal v-model:open="first" title="First modal" :ui="{ footer: 'justify-end' }">
<UButton color="neutral" variant="subtle" label="Open" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="first = false" />
<UModal v-model:open="second" title="Second modal" :ui="{ footer: 'justify-end' }">
<UButton label="Open second" color="neutral" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="second = false" />
</template>
</UModal>
</template>
</UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const first = ref(false)
const second = ref(false)
</script>
<template>
<UModal v-model:open="first" title="First modal" :ui="{ footer: 'justify-end' }">
<UButton color="neutral" variant="subtle" label="Open" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="first = false" />
<UModal v-model:open="second" title="Second modal" :ui="{ footer: 'justify-end' }">
<UButton label="Open second" color="neutral" />
<template #footer>
<UButton label="Close" color="neutral" variant="outline" @click="second = false" />
</template>
</UModal>
</template>
</UModal>
</template>
Met voetgangerssleuf
Gebruik de #footer-sleuf om inhoud toe te voegen na de body van de Modal.
<script setup lang="ts">
const open = ref(false)
</script>
<template>
<UModal v-model:open="open" title="Modal with footer" description="This is useful when you want a form in a Modal." :ui="{ footer: 'justify-end' }">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
<template #footer="{ close }">
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
<UButton label="Submit" color="neutral" />
</template>
</UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<UModal v-model:open="open" title="Modal with footer" description="This is useful when you want a form in a Modal." :ui="{ footer: 'justify-end' }">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
<template #footer="{ close }">
<UButton label="Cancel" color="neutral" variant="outline" @click="close" />
<UButton label="Submit" color="neutral" />
</template>
</UModal>
</template>
Met opdrachtpalet
U kunt een CommandPalette -component gebruiken in de inhoud van de modus.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
key: 'modal-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>
<UModal @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>
</UModal>
</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: 'modal-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>
<UModal @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>
</UModal>
</template>
useLazyFetch met immediate: false om alleen gegevens op te halen wanneer de modus wordt geopend.API
Props
| Prop | Default | Type |
|---|---|---|
title | string | |
description | string | |
content | DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>The content of the modal. | |
overlay | true | boolean Render an overlay behind the modal. |
scrollable | false | boolean When |
transition | true | boolean Animate the modal when opening or closing. |
fullscreen | false | boolean When |
portal | true | string | false | true | HTMLElementRender the modal in a portal. |
close | true | boolean | Omit<ButtonProps, LinkPropsKeys> Display a close button to dismiss the modal.
|
closeIcon | appConfig.ui.icons.close | anyThe icon displayed in the close button. |
dismissible | true | boolean When |
open | boolean The controlled open state of the dialog. Can be binded as | |
defaultOpen | boolean The open state of the dialog when it is initially rendered. Use when you do not need to control its open state. | |
modal | true | boolean The modality of the dialog When set to |
unmountOnHide | true | boolean When set to |
ui | { overlay?: SlotClass; content?: SlotClass; header?: SlotClass; wrapper?: SlotClass; body?: SlotClass; footer?: SlotClass; title?: SlotClass; description?: SlotClass; close?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
default | { open: boolean; } |
content | { close: () => void; } |
header | { close: () => void; } |
title | {} |
description | {} |
actions | {} |
close | { ui: object; } |
body | { close: () => void; } |
footer | { close: () => void; } |
Uitzendt
| Event | Type |
|---|---|
leave | [] |
after:leave | [] |
enter | [] |
after:enter | [] |
close:prevent | [] |
update:open | [value: boolean] |
Thema
export default defineAppConfig({
ui: {
modal: {
slots: {
overlay: 'fixed inset-0',
content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
wrapper: '',
body: 'flex-1 p-4 sm:p-6',
footer: 'flex items-center gap-1.5 p-4 sm:px-6',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
close: 'absolute top-4 end-4'
},
variants: {
transition: {
true: {
overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
content: 'data-[state=open]:animate-[scale-in_200ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_200ms_var(--ease-out)]'
}
},
fullscreen: {
true: {
content: 'inset-0'
},
false: {
content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
}
},
overlay: {
true: {
overlay: 'bg-elevated/75'
}
},
scrollable: {
true: {
overlay: 'overflow-y-auto',
content: 'relative'
},
false: {
content: 'fixed',
body: 'overflow-y-auto'
}
}
},
compoundVariants: [
{
scrollable: true,
fullscreen: false,
class: {
overlay: 'grid place-items-center p-4 sm:py-8'
}
},
{
scrollable: false,
fullscreen: false,
class: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
}
}
]
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
modal: {
slots: {
overlay: 'fixed inset-0',
content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
wrapper: '',
body: 'flex-1 p-4 sm:p-6',
footer: 'flex items-center gap-1.5 p-4 sm:px-6',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
close: 'absolute top-4 end-4'
},
variants: {
transition: {
true: {
overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
content: 'data-[state=open]:animate-[scale-in_200ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_200ms_var(--ease-out)]'
}
},
fullscreen: {
true: {
content: 'inset-0'
},
false: {
content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
}
},
overlay: {
true: {
overlay: 'bg-elevated/75'
}
},
scrollable: {
true: {
overlay: 'overflow-y-auto',
content: 'relative'
},
false: {
content: 'fixed',
body: 'overflow-y-auto'
}
}
},
compoundVariants: [
{
scrollable: true,
fullscreen: false,
class: {
overlay: 'grid place-items-center p-4 sm:py-8'
}
},
{
scrollable: false,
fullscreen: false,
class: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
}
}
]
}
}
})
]
})