Api

Global events

Subscribe to modal-open and modal-close events globally or per group — onModalOpen, onModalClose.

Global events

The package emits two global lifecycle events you can subscribe to from anywhere — outside any specific modal component. Both come in a global form (every modal) and a group-scoped form (one group only), and both return an unsubscribe function.

import { onModalOpen, onModalClose } from '@kolirt/vue-modal'
EventFires when…Handler argument
onModalOpenA modal has just been pushed onto the stack.ModalItem
onModalCloseA modal has just been finalized and removed from the stack.ModalItem
For per-instance events emitted by a modal component (via Vue emit('eventName', …)), use ModalHandle.on/off, the on option of openModal, or useModal(...).on. For pre-close interception inside the modal itself, use onBeforeClose from useModalContext. The functions on this page are the only global listeners exposed by the package.

onModalOpen(handler) → () => void / onModalOpen(group, handler) → () => void

Subscribes to modal-open events. The handler runs synchronously every time a modal is pushed onto the stack, immediately after it is added to state.

Signature

type ModalOpenHandler = (modal: ModalItem) => void

function onModalOpen(handler: ModalOpenHandler): () => void
function onModalOpen(group: ModalGroup, handler: ModalOpenHandler): () => void

The ModalItem passed to the handler exposes id, group, component, props, and listeners — see Types.

Parameters

ParameterTypeRequiredDescription
groupModalGroupnoScopes the subscription to a single group (typed by your ModalGroupRegistry). Omit for a global subscription across all groups.
handlerModalOpenHandleryesCalled with the freshly opened ModalItem. Errors thrown inside the handler are caught and logged via console.error — they never break the open flow or other subscribers.

Example — close one group when another opens

import { onModalOpen, closeModalsByGroup } from '@kolirt/vue-modal'

// Whenever a 'main' modal opens, drop everything in 'sidebar'.
const off = onModalOpen('main', () => {
  closeModalsByGroup('sidebar', { ignoreGuard: true })
})

// later:
off() // unsubscribe

Example — global telemetry

onModalOpen((modal) => {
  analytics.track('modal_open', {
    id: modal.id,
    group: modal.group,
    component: (modal.component as { __name?: string }).__name
  })
})
Handlers fire after the modal is pushed to state — so reading modals.value or groupModals(g).value from inside the handler already includes the newly opened modal. The call is synchronous, so it's safe to act on the open immediately (e.g. close other groups) without waiting for nextTick.

onModalClose(handler) → () => void / onModalClose(group, handler) → () => void

Subscribes to modal-close events. The handler runs synchronously when a modal is finalized — after beforeClose guards have passed, the exit animation completed (or was skipped via instantExit), and the modal has been removed from the stack. It fires exactly once per modal instance, regardless of close cause (user dismiss, confirm(...), closeModalById, bulk close, replaceModal).

Signature

type ModalCloseHandler = (modal: ModalItem) => void

function onModalClose(handler: ModalCloseHandler): () => void
function onModalClose(group: ModalGroup, handler: ModalCloseHandler): () => void

Parameters

ParameterTypeRequiredDescription
groupModalGroupnoScopes the subscription to a single group. Omit for a global subscription.
handlerModalCloseHandleryesCalled with the just-finalized ModalItem (the same shape onModalOpen received earlier). Errors thrown inside the handler are caught and logged — they never block the modal promise resolution.

Timing relative to the modal promise

onModalClose fires before the openModal / useModal.open promise resolves or rejects. If you await openModal(...) and also subscribe via onModalClose, the global handler runs first.

const handle = openModal(MyDialog, { group: 'main' })

onModalClose('main', (m) => {
  if (m.id === handle.id) console.log('global handler runs first')
})

try {
  await handle
} finally {
  console.log('handle settled second')
}

Example — restore focus on close

import { onModalClose } from '@kolirt/vue-modal'

let lastTrigger: HTMLElement | null = null

document.addEventListener('click', (e) => {
  if ((e.target as HTMLElement).matches('[data-open-modal]')) {
    lastTrigger = e.target as HTMLElement
  }
})

onModalClose(() => {
  lastTrigger?.focus()
  lastTrigger = null
})

Example — clean up per-group side effects

import { onModalOpen, onModalClose } from '@kolirt/vue-modal'

const offOpen = onModalOpen('overlay', () => document.body.classList.add('has-overlay'))
const offClose = onModalClose('overlay', (_modal) => {
  // last 'overlay' modal closed? drop the class
  // (use `groupModals('overlay').value.length` if you import it)
  document.body.classList.remove('has-overlay')
})

Symmetry and lifetime

  • Both subscriptions live for the lifetime of the JavaScript module unless you call the returned unsubscribe function. They are not tied to a Vue component scope — if you call them inside setup(), register the cleanup yourself in onScopeDispose / onBeforeUnmount.
  • Handlers are invoked in registration order. Global subscribers fire first, then group-scoped subscribers for the matching group.
  • One throwing handler does not affect others: each handler is wrapped in try/catch and exceptions are reported via console.error.

When to prefer reactive state instead

If you only need to render derived UI from the open stack, prefer the reactive State helpers (modals, isOpened, groupModals, isGroupOpen) and watch them. The event API is for side effects that must run exactly when the transition happens — analytics, focus restoration, cross-group coordination, manual DOM tweaks.

Copyright © 2026