Opening & closing
Opening & closing
All action functions are top-level exports from @kolirt/vue-modal. They operate on the global modal stack.
openModal
Signature
function openModal<T = unknown, C extends Component = Component>(
component: C,
options?: OpenModalOptions<C>
): ModalHandle<T>
interface OpenModalOptions<C extends Component> {
props?: ExtractComponentProps<C> // typed props forwarded to the component
on?: Record<string, (...args: any[]) => void> // event listeners
group?: ModalGroup // target ModalTarget; falls back to component's defineOptions({ modalGroup })
instantEnter?: boolean // skip enter animation (default false)
}
Mounts component and returns a ModalHandle<T> — a promise enriched with id, group, close, on, off. The promise resolves when the modal calls confirm(data) and rejects with ModalClosedError on dismiss.
import { openModal } from '@kolirt/vue-modal'
import EditDialog from './EditDialog.vue'
const result = await openModal<{ name: string }>(EditDialog, {
props: { userId: 42 }
}).catch(() => null)
if (result) console.log('saved', result.name)
A group is required — either via options.group or defineOptions({ modalGroup: '...' }) on the component.
closeModal
Signature
function closeModal<T = unknown>(
opts?: CloseModalOptions<T> & { group?: ModalGroup }
): Promise<void>
interface CloseModalOptions<T = unknown> {
success?: boolean // false → reject (default), true → resolve with `data`
data?: T // value passed to resolve when success: true
ignoreGuard?: boolean // skip every onBeforeClose guard
instantExit?: boolean // skip exit animation, finalize synchronously
}
// + optional `group` here narrows the target to one stack
Closes the topmost modal globally, or the topmost in a specific group when group is supplied. Returns a promise that resolves once the close intent has been processed (guards awaited, animation pending or skipped).
await closeModal() // topmost across all groups
await closeModal({ group: 'confirm' }) // topmost in 'confirm'
await closeModal({ success: true, data: 42 }) // resolve instead of reject
closeModalById
Signature
function closeModalById<T = unknown>(
id: number, // modal instance id (handle.id, controller.instanceId)
opts?: CloseModalOptions<T>
): Promise<void>
// CloseModalOptions<T> shape — see closeModal above.
Close a specific modal by id — useful when the modal is not the topmost. No-op if no such modal exists.
const handle = openModal(MyDialog)
// later …
await closeModalById(handle.id, { success: true, data: 'ok' })
closeModalsByGroup / closeAllModals
Signature
function closeModalsByGroup(
group: ModalGroup, // restrict close to this stack
opts?: CloseFlags
): Promise<{ closed: number; vetoed: number }>
function closeAllModals(
opts?: CloseFlags
): Promise<{ closed: number; vetoed: number }>
interface CloseFlags {
ignoreGuard?: boolean // skip every onBeforeClose guard
instantExit?: boolean // skip exit animations
}
Close every modal in a group (top-down) or across the entire app. Both accept CloseFlags — bulk-close functions never resolve, only reject, so success/data aren't part of the shape. The result reports how many were closed vs. blocked by onBeforeClose guards. Useful in route guards:
router.beforeEach(async () => {
await closeAllModals({ ignoreGuard: true, instantExit: true })
})
replaceModal
Signature
function replaceModal<T = unknown, C extends Component = Component>(
component: C,
options?: OpenModalOptions<C>
): ModalHandle<T>
// OpenModalOptions<C> shape — see openModal above.
Closes the topmost modal of the target group instantly (skipping its exit animation, ignoring guards) and opens component. Modals in other groups are left untouched. Use it for wizard step swaps:
replaceModal(StepTwo, {
props: { step: 2 },
instantEnter: true
})
Options
OpenModalOptions<C>
Accepted by openModal, replaceModal, and useModal(...).open().
| Property | Type | Default | Description |
|---|---|---|---|
props | ExtractComponentProps<C> | {} | Forwarded to the component as v-bind. Type-checked against the component's defineProps — required props become required keys. |
on | Record<string, (...args) => void> | {} | Listeners forwarded to the component. Each key is an emit name (e.g. progress, update:value). Listeners run on every emit until the modal closes. |
group | ModalGroup | component.modalGroup if set | Which <ModalTarget> mounts the modal. Resolved as options.group ?? defineOptions({ modalGroup }).modalGroup. Throws if neither is provided — there is no fallback. |
instantEnter | boolean | false | Skip the enter animation. Adds data-instant="" to <ModalContent> and the topmost <ModalOverlay>; bundled CSS scoped to [data-state="open"] suppresses the animation. Exit animations still run. |
ExtractComponentProps<C> is the package's helper that strips Vue's internal props (key, ref, etc.) so only your component's own defineProps shape remains. With <script setup lang="ts"> it's fully inferred — TypeScript will refuse openModal(EditDialog, { props: { wrong: 1 } }) if EditDialog doesn't expose wrong.
CloseModalOptions<T>
Accepted by closeModal, closeModalById, handle.close(), and the modal-side useModalContext().close(opts).
| Property | Type | Default | Description |
|---|---|---|---|
success | boolean | false | Determines how the modal's promise settles. true → resolves with data. false → rejects with a ModalClosedError (this is what users get when they dismiss the modal — Esc, overlay click, close button). |
data | T | — | Value passed to resolve when success: true. Ignored otherwise. Typed by the T you pass to openModal<T>(...). |
ignoreGuard | boolean | false | Skip every registered onBeforeClose async guard. Use for hard close (route guard, app teardown). Without it, any guard returning false cancels the close. |
instantExit | boolean | false | Finalize synchronously without waiting for the exit animation. The DOM node unmounts immediately. Useful for tests and programmatic flows where the user shouldn't see the modal flicker out. |
closeModal adds one extra property on top of CloseModalOptions:
| Property | Type | Default | Description |
|---|---|---|---|
group | ModalGroup | — | When set, only the topmost modal of that group is closed. When omitted, the topmost modal across all groups is closed. |
CloseFlags
A subset of CloseModalOptions accepted by bulk-close functions: closeAllModals(opts?) and closeModalsByGroup(group, opts?). These functions never resolve a modal — they always reject the affected promises — so success / data are not part of the shape.
| Property | Type | Default | Description |
|---|---|---|---|
ignoreGuard | boolean | false | Same as on CloseModalOptions. Pass true to bypass every guard. |
instantExit | boolean | false | Same as on CloseModalOptions. Skip exit animations on every closure. |
Both bulk functions resolve with { closed: number, vetoed: number } — vetoed counts modals whose guards returned false when ignoreGuard was not set.
The ModalHandle API
interface ModalHandle<T> extends Promise<T> {
id: number
group: ModalGroup
close(opts?: CloseModalOptions<T>): void
on(event: string, handler: (...args: any[]) => void): void
off(event: string, handler: (...args: any[]) => void): void
}
Use await, .then, .catch like any promise. Attach extra listeners after opening:
const handle = openModal(EditDialog)
handle.on('progress', (pct: number) => updateProgressBar(pct))
handle.catch(() => null)
const result = await handle
| Where you are | How to close |
|---|---|
| Inside the modal | useModalContext().close() / confirm() |
| Outside, by group | closeModal({ group: 'default' }) |
| Outside, by id | closeModalById(handle.id, opts) |
| Outside, via handle | handle.close(opts) |
Pitfalls
Forgetting to handle dismiss. openModal rejects when the user dismisses. Without .catch() you get an unhandled rejection on every cancel.
// bad — crashes on dismiss
const result = await openModal(MyDialog)
// good
const result = await openModal(MyDialog).catch(() => null)
Double-close is a no-op. Once a modal is closing, further close() / confirm() calls are silently ignored.
