Guide

Writing a modal

Structure of a modal component using ModalRoot, ModalContent, and useModalContext.

Writing a modal

A modal is a regular Vue SFC. Wrap your markup in <ModalRoot> + <ModalContent>, then use useModalContext<T>() to resolve or reject the promise returned by openModal.

Required structure

<ModalRoot>            ← dialog root; manages presence, ARIA, events
  <ModalContent>       ← visible card; drives data-state animations
    <ModalTitle>       ← labels the dialog
    <ModalDescription> ← describes the dialog
    …content…
  </ModalContent>
</ModalRoot>

<ModalRoot> must contain a <ModalContent>. The exit-animation wait lives inside <ModalContent> — without it, the modal hangs in the DOM forever.

Complete example

ConfirmDialog.vue
<script setup lang="ts">
import {
  ModalContent,
  ModalDescription,
  ModalRoot,
  ModalTitle,
  useModalContext
} from '@kolirt/vue-modal'

defineOptions({ modalGroup: 'default' })

const props = defineProps<{
  title: string
  message: string
}>()

const { close, confirm } = useModalContext<boolean>()
</script>

<template>
  <ModalRoot class="root">
    <ModalContent class="card">
      <ModalTitle>{{ props.title }}</ModalTitle>
      <ModalDescription>{{ props.message }}</ModalDescription>

      <div class="actions">
        <button @click="close()">Cancel</button>
        <button @click="confirm(true)">OK</button>
      </div>
    </ModalContent>
  </ModalRoot>
</template>

<style scoped>
.root {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1rem;
}
.card {
  width: 100%;
  max-width: 24rem;
  padding: 1.5rem;
  border-radius: 0.5rem;
  background: white;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1.5rem;
}
</style>

Open it from anywhere:

import { openModal } from '@kolirt/vue-modal'
import ConfirmDialog from './ConfirmDialog.vue'

const ok = await openModal<boolean>(ConfirmDialog, {
  props: { title: 'Delete?', message: 'This cannot be undone.' }
}).catch(() => false)

Because modalGroup: 'default' is declared on the component, group doesn't need to be passed at the call site.

What useModalContext gives you

const {
  id,               // number — unique id of this modal instance
  group,            // ModalGroup — the group this modal belongs to
  isClosing,        // ComputedRef<boolean> — exit animation in progress
  isTopmost,        // ComputedRef<boolean> — topmost within its group
  isTopmostGlobal,  // ComputedRef<boolean> — topmost across all groups
  effectiveOptions, // ComputedRef<ModalEffectiveOptions> — merged behavior
  close,            // (opts?) => void — reject the promise
  confirm,          // (data, opts?) => void — resolve the promise
  onBeforeClose     // (handler) => void — register a close guard
} = useModalContext<boolean>()

useModalContext() throws if called outside a modal component. See the Modal context page for a full reference.

Copyright © 2026