Modal
Usage
Modal의 기본 슬롯에 [Button](/docs/components/buttonxph04x 또는 다른 구성 요소를 사용합니다.
그런 다음 #content 슬롯을 사용하여 모달이 열려 있을 때 표시된 내용을 추가합니다.
<template>
<UModal>
<UButton label="Open" color="neutral" variant="subtle" />
<template #content>
<Placeholder class="h-48 m-4" />
</template>
</UModal>
</template>또한 #header, #body 및 #footer 슬롯을 사용하여 Modal 콘텐츠를 사용자 정의할 수 있습니다.
Title 파일
title prop 을 사용하여 Modal 헤더의 제목을 설정합니다.
<template>
<UModal title="Modal with title">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>설명
description prop을 사용하여 Modal 헤더에 대한 설명을 설정합니다.
<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>닫기
close Prop을 사용하여 Modal 헤더에 표시되는 닫기 버튼(false 값)을 사용자 정의하거나 숨깁니다.
Button 구성 요소의 모든 속성을 전달하여 사용자 정의할 수 있습니다.
<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 슬롯이 헤더의 일부로 사용되는 경우에는 닫기 버튼이 표시되지 않습니다.닫기 아이콘
close-icon 소품을 사용하여 닫기 단추 Icon를 사용자 정의합니다. 기본값은 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>Transition
transition 소품을 사용하여 모달의 애니메이션 여부를 제어합니다. 기본값은 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
overlay 소품을 사용하여 모달에 오버레이가 있는지 여부를 제어합니다. 기본값은 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>Modal 모델
modal 소품을 사용하여 Modal이 외부 내용과의 상호 작용을 차단할지 여부를 제어합니다. 기본값은 true입니다.
modal가 false로 설정되면 오버레이가 자동으로 비활성화되고 외부 내용이 대화형으로 전환됩니다.<template>
<UModal :modal="false" title="Modal interactive">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>사용할 수 없음
dismissible 소품을 사용하여 모달 바깥쪽을 클릭하거나 escape 키를 누를 때 모달이 허용되지 않도록 제어합니다. 기본값은 true입니다.
close:prevent 이벤트는 사용자가 닫으려고 할 때 내보내집니다.modal: false와 dismissible: false를 결합하여 Modal의 배경을 닫지 않고도 대화형으로 만들 수 있습니다.<template>
<UModal :dismissible="false" title="Modal non-dismissible">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-48" />
</template>
</UModal>
</template>Scrollable: badge{label="4.2+" class="align-text-top"} 스크롤 가능
scrollable Prop을 사용하여 Modal의 내용을 오버레이 내에서 스크롤할 수 있도록 합니다.
modal: false는 호환되지 않으며 overlay: false는 배경만 제거합니다.<template>
<UModal scrollable title="Modal scrollable">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>Fullscreen 화면
fullscreen prop을 사용하여 Modal 전체 화면을 만듭니다.
<template>
<UModal fullscreen title="Modal fullscreen">
<UButton label="Open" color="neutral" variant="subtle" />
<template #body>
<Placeholder class="h-full" />
</template>
</UModal>
</template>Unmount: badge{label="4.10+" class="align-text-top"} 마운트 해제
unmount-on-hide 소품을 사용하여 모달이 닫혀 있을 때 마운트 해제되지 않도록 합니다. 기본값은 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이 false로 설정되면 내용도 서버에서 렌더링됩니다. SSR 중에 페이지 로드 시 플래시 없이 열린 모드를 렌더링하거나 SEO에 해당 콘텐츠를 노출하는 데 유용합니다.examples 예제
Control 오픈 상태
default-open prop 또는 v-model:open 지시문을 사용하여 오픈 상태를 제어할 수 있습니다.
<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를 사용하여 O를 눌러 모달을 전환할 수 있습니다.Programmatic 사용법
useOverlay 컴포지블을 사용하여 모드를 프로그래밍 방식으로 열 수 있습니다.
OverlayProvider 구성 요소를 사용하는 [App](](/docs/components/app) 구성 요소로 래핑해야 합니다.먼저 프로그래밍 방식으로 열 모달 컴포넌트를 만듭니다.
<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 이벤트가 발생합니다. close 이벤트를 통해 데이터를 내보낼 수 있으며 해당 데이터는 open()의 확인된 값이 됩니다. promise가 해결하려면 이벤트를 내보내야 합니다.그런 다음 앱에서 사용하십시오.Use it in your 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')를 방출하여 모달 구성 요소 내에서 모달을 닫을 수 있습니다.nested modals 예제
당신은 서로 안에 modals를 중첩 할 수 있습니다.
<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>
바닥글 슬롯 포함
#footer 슬롯을 사용하여 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>
xPH323xWith 명령 팔레트
Modal 콘텐츠 내에서 CommandPalette 구성 요소를 사용할 수 있습니다.
<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와 immediate: false를 사용하여 Modal이 열릴 때만 데이터를 가져옵니다.API
Props (### 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; } |
슬롯
| Slot | Type |
|---|---|
default | { open: boolean; } |
content | { close: () => void; } |
header | { close: () => void; } |
title | {} |
description | {} |
actions | {} |
close | { ui: object; } |
body | { close: () => void; } |
footer | { close: () => void; } |
Emits
| Event | Type |
|---|---|
leave | [] |
after:leave | [] |
enter | [] |
after:enter | [] |
close:prevent | [] |
update:open | [value: boolean] |
Theme 테마
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'
}
}
]
}
}
})
]
})