Use the auto-imported useOverlay composable to programmatically control Modal and Slideover components.
<script setup lang="ts">
import { LazyModalExample } from '#components'
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)
async function openModal() {
modal.open()
}
</script>
useOverlay composable is created using createSharedComposable, ensuring that the same overlay state is shared across your entire application.overlay.open() can be awaited. In order for this to work, however, the overlay component must emit a close event. See example below for details.useOverlay()
The useOverlay composable provides methods to manage overlays globally. Each created overlay returns an instance with its own methods.
create(component: T, options: OverlayOptions): OverlayInstance
Create an overlay, and return a factory instance.
false.false.::
:: ::
open(id: symbol, props?: ComponentProps<T>): OpenedOverlay<T>
Open an overlay by its id.
close(id: symbol, value?: any): void
Close an overlay by its id.
patch(id: symbol, props: ComponentProps<T>): void
Update an overlay by its id.
unmount(id: symbol): void
Remove an overlay from the DOM by its id.
isOpen(id: symbol): boolean
Check if an overlay is open using its id.
overlays: Overlay[]
In-memory list of all overlays that were created.
open(props?: ComponentProps<T>): Promise<OpenedOverlay<T>>
Open the overlay.
<script setup lang="ts">
import { LazyModalExample } from '#components'
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample)
function openModal() {
modal.open({
title: 'Welcome'
})
}
</script>
close(value?: any): void
Close the overlay.
patch(props: ComponentProps<T>): void
Update the props of the overlay.
<script setup lang="ts">
import { LazyModalExample } from '#components'
const overlay = useOverlay()
const modal = overlay.create(LazyModalExample, {
title: 'Welcome'
})
function openModal() {
modal.open()
}
function updateModalTitle() {
modal.patch({ title: 'Updated Title' })
}
</script>
Here's a complete example of how to use the useOverlay composable:
<script setup lang="ts">
import { ModalA, ModalB, SlideoverA } from '#components'
const overlay = useOverlay()
// Create with default props
const modalA = overlay.create(ModalA, { title: 'Welcome' })
const modalB = overlay.create(ModalB)
const slideoverA = overlay.create(SlideoverA)
const openModalA = () => {
// Open modalA, but override the title prop
modalA.open({ title: 'Hello' })
}
const openModalB = async () => {
// Open modalB, and wait for its result
const modalBInstance = modalB.open()
const input = await modalBInstance
// Pass the result from modalB to the slideover, and open it
slideoverA.open({ input })
}
</script>
<template>
<button @click="openModalA">Open Modal</button>
</template>
In this example, we're using the useOverlay composable to control multiple modals and slideovers.
When opening overlays programmatically (e.g. modals, slideovers, etc), the overlay component can only access injected values from the component containing UApp (typically app.vue or layout components). This is because overlays are mounted outside of the page context by the UApp component.
As such, using provide() in pages or parent components isn't supported directly. To pass provided values to overlays, the recommended approach is to use props instead:
<script setup lang="ts">
import { LazyModalExample } from '#components'
const overlay = useOverlay()
const providedValue = inject('valueProvidedInPage')
const modal = overlay.create(LazyModalExample, {
props: {
providedValue,
otherData: someValue
}
})
</script>
Create a custom useDialog composable that wraps useOverlay to simplify common dialog patterns. This approach enables opinionated dialogs tailored to specific business requirements and design preferences. The following example demonstrates a reusable confirm dialog:
<script lang="ts" setup>
interface DialogConfirmProps {
title?: string
description?: string
onConfirm?: () => void
onDismiss?: () => void
}
const props = withDefaults(defineProps<DialogConfirmProps>(), {
onConfirm: () => {},
onDismiss: () => {},
})
const emits = defineEmits<{
close: [value?: any]
}>()
const handleConfirm = () => {
props.onConfirm()
emits("close")
}
const handleDismiss = () => {
props.onDismiss()
emits("close")
}
</script>
<template>
<UModal :dismissible="false" :close="false">
<template #header>
<div class="relative w-full flex items-start gap-3">
<div class="flex-1 min-w-0">
<div class="text-base font-semibold">{{ title }}</div>
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">{{ description }}</div>
</div>
<UButton icon="i-lucide-x" @click="handleDismiss" />
</div>
</template>
<template #footer>
<div class="flex gap-4">
<UButton label="No" @click="handleDismiss" />
<UButton label="Yes" @click="handleConfirm" />
</div>
</template>
</UModal>
</template>
import DialogConfirm from "#components"
export interface DialogConfirmOptions {
title: string
description?: string
onConfirm?: () => void
onDismiss?: () => void
}
export const useDialog = () => {
const overlay = useOverlay()
const confirm = (options: DialogConfirmOptions): void => {
const modal = overlay.create(DialogConfirm, {
destroyOnClose: true,
props: options,
})
modal.open()
}
return { confirm }
}
<script setup lang="ts">
const { confirm } = useDialog()
const handleDelete = () => {
confirm({
title: "Delete Item",
description: "Are you sure you want to delete this item?",
onConfirm: () => {
// User clicked "Yes"
console.log("Item deleted")
deleteItem()
},
onDismiss: () => {
// User clicked "No" or X button
console.log("Deletion cancelled")
}
})
}
</script>
<template>
<UButton @click="handleDelete" label="Delete Item" />
</template>