<VueFinalDialog>
<VueFinalDialog> renders a native <dialog> and opens it with showModal(). It plugs into useModal(), v-model and useVfm() like <VueFinalModal>, and its open() and close() resolve with the same results.
The browser does the rest: the dialog sits in the top layer, so it needs no z-index, the page behind it is inert, Esc closes it, the focus moves into it on open and back on close. <VueFinalDialog> ships no styles and no transitions of its own.
When to use it
<VueFinalModal> stays the default of useModal() and the one to customize: it is a plain element you can place, layer and animate as you like. Use <VueFinalDialog> when you want what the browser gives a modal <dialog>: focus moved in and back, the page behind it inert and hidden from screen readers, and on Android the back gesture closing it. Simple confirmations and forms are a good fit.
The top layer and the inert page come with limits that <VueFinalModal> does not have:
- Nothing outside the dialog can be used while it is open, whatever its
z-index. A dropdown, a date picker or a toast that a UI library teleports to<body>shows behind the dialog and cannot be clicked: point its teleport target at the dialog instead. A<VueFinalModal>is moved in for you, see Next to<VueFinalModal>. - The page behind a modal dialog cannot stay interactive, like with
background: 'interactive'. A non-modal dialog, withmodal: false, leaves it interactive but is not in the top layer either. - A modal dialog always covers the whole viewport: it cannot be kept inside a container, like
<VueFinalModal>withteleportTo: false. - It opens once the app has hydrated, so it is never part of the first paint, see Server-side rendering.
z-indexand the JavaScript hooks of Vue transitions have no effect: dialogs stack in the order they opened and animate with CSS.
Template structure
<template>
<dialog :closedby="closedby">
<slot :close="close" />
</dialog>
</template>
Every attr you pass, such as class or aria-labelledby, falls through to the <dialog>.
Usage
With useModal()
import { useModal, VueFinalDialog } from 'vue-final-modal'
const { open, close } = useModal({
component: VueFinalDialog,
attrs: {
class: 'my-dialog',
closedby: 'any',
onClosed() { /* on closed */ },
},
slots: { default: '<p>Hello from a native dialog</p>' },
})
await open() // 'opened'
With v-model
<script setup lang="ts">
import { ref } from 'vue'
import { VueFinalDialog } from 'vue-final-modal'
const show = ref(false)
</script>
<template>
<button @click="show = true">
Open
</button>
<VueFinalDialog v-model="show" class="my-dialog">
<template #default="{ close }">
<p>Hello from a native dialog</p>
<button @click="close">
Close
</button>
</template>
</VueFinalDialog>
</template>
Esc, a click outside with closedby="any" and a <form method="dialog"> all close the dialog in the browser, and <VueFinalDialog> writes false back to v-model.
Transitions
Animate the dialog and its ::backdrop with CSS. opened and closed, and so open() and close(), wait for the transitions and animations that run on the <dialog> itself and on its ::backdrop, so a dialog opened by useModal() is unmounted only after it faded out:
.my-dialog {
opacity: 1;
transition: opacity .3s, overlay .3s allow-discrete, display .3s allow-discrete;
}
.my-dialog:not([open]) {
opacity: 0;
}
@starting-style {
.my-dialog[open] {
opacity: 0;
}
}
The animations of the content inside the dialog, and animations that never end, are not waited for.
Props
modelValue (optional)
Display the dialog or not.
- Type:
- JS:
Boolean - TS:
PropType<boolean>
- JS:
- Default:
undefined
modalId (optional)
An uniq value to open and close the dialog with useVfm(): open(modalId), close(modalId), toggle(modalId) and closeAll().
- Type:
- JS:
[String, Number, Symbol] - TS:
PropType<ModalId>
- JS:
- Default:
undefined
modal
Open the dialog with showModal(). Set it to false to open it with show() instead: a non-modal dialog leaves the page interactive and is not in the top layer. It applies the next time the dialog opens.
- Type:
- JS:
Boolean - TS:
PropType<boolean>
- JS:
- Default:
true
closedby (optional)
The native closedby attribute: 'any' also closes the dialog on a click outside, 'none' keeps it open on Esc. Browsers that do not support it ignore it.
- Type:
- JS:
String - TS:
PropType<'any' | 'closerequest' | 'none'>
- JS:
- Default:
undefined
lockScroll
Lock the scroll of the page while the dialog is open, with @hunterliu/scroll-lock. A modal dialog makes the page inert but does not stop it from scrolling. The page keeps the room of its scrollbar, so its content does not shift.
- Type:
- JS:
Boolean - TS:
PropType<boolean>
- JS:
- Default:
false
Events
(e: 'beforeOpen', event: { stop: () => void }): void: Emits before the dialog opens.event.stop()keeps it closed. The pendingopen()then resolves with'stopped'instead of'opened'.
(e: 'opened'): void: Emits after the dialog opened and its transitions ended.(e: 'beforeClose', event: { stop: () => void }): void: Emits before the dialog closes, also onEscand on a click outside withclosedby="any".event.stop()keeps it open. The pendingclose()then resolves with'stopped'instead of'closed'.- On
Escthe browser lets the page keep the dialog open only after the user interacted with the page, and not for a secondEscin a row: then the dialog closes anyway. - A
<form method="dialog">closes the dialog without asking, sobeforeCloseis not emitted for it.
(e: 'closed'): void: Emits after the dialog closed and its transitions ended. A dialog opened byuseModal()is unmounted right after, unless it haskeepAlive.(e: 'cancel', event: Event): void: The nativecancelevent of a close request, such asEsc.event.preventDefault()keeps the dialog open without askingbeforeClose, within the same limits as above.(e: 'close', event: Event): void: The nativecloseevent, fired once the<dialog>closed and before its transitions end.
Read the returnValue of a <form method="dialog"> in close:
useModal({
component: VueFinalDialog,
attrs: {
onClose: (event: Event) => console.log((event.target as HTMLDialogElement).returnValue),
},
})
Slots
- The
defaultslot renders the content of the dialog. Itsclosecloses the dialog.
Server-side rendering
The dialog is rendered closed on the server, with its content, and opens once the app has hydrated: an open <dialog> in the server HTML could not become a modal one. open() resolves on the server right away, like for <VueFinalModal>.
Next to <VueFinalModal>
A modal dialog makes everything outside it inert, so a <VueFinalModal> opened while a modal <VueFinalDialog> is open is rendered inside the topmost dialog instead of its teleportTo target. It can be used there, and Esc closes it alone: the dialog closes on the next Esc. With escToClose false, Esc closes neither. A <VueFinalModal> with teleportTo: false stays where it is rendered.
Escreaches the modal that has the focus, which its focus trap moves in once it has opened.- The browser keeps the dialog open on
Esconly after the user interacted with the page, as for beforeClose. A modal opened by a click, such as a confirm, always is. - While the dialog runs a
transformtransition, the modal inside it is laid out within the dialog until the transition ends.
Another <VueFinalDialog> opened on top of a dialog needs none of this: the browser stacks dialogs in the order they opened, only the topmost one can be used, and Esc closes one at a time.
Your own dialog component
<VueFinalDialog> is built on useModalLifecycle(), which you can use to build your own modal component that useModal() can open and await.