Guide

TypeScript

Full type-safety — group registry, typed promises, ExtractComponentProps, and useModalContext typing.

TypeScript

The package is written in TypeScript and ships full type declarations. This page covers the patterns that give end-to-end type safety.

Registering groups

Groups are validated at compile time via module augmentation. Without this step the package cannot be used — ModalGroup resolves to never and every group reference is a type error.

main.ts
import { type DefineGroups } from '@kolirt/vue-modal'

declare module '@kolirt/vue-modal' {
  interface ModalGroupRegistry extends DefineGroups<['default', 'confirm', 'panel']> {}
}

After augmentation every group field in openModal, useModal, closeModalsByGroup, <ModalTarget>, and createModal only accepts 'default' | 'confirm' | 'panel'.

openModal(MyDialog, { group: 'default' })   // ✓
openModal(MyDialog, { group: 'unknown' })   // ✗ Type error

Typed results — openModal<TResult>

The first generic is the type the modal resolves with:

const result = await openModal<{ id: number; name: string }>(SaveDialog, {
  group: 'default'
}).catch(() => null)

result?.id   // number
result?.name // string

Inside the component, useModalContext<TResult>() must use the same type:

const { confirm } = useModalContext<{ id: number; name: string }>()
confirm({ id: 1, name: 'Alice' })   // ✓
confirm({ id: 1 })                  // ✗ missing name

The type is not inferred from the component automatically — you annotate it explicitly on both sides.

Typed props

openModal's props option mirrors exactly what defineProps declares on the component:

ConfirmDialog.vue
<script setup lang="ts">
const props = defineProps<{
  title: string
  message?: string
}>()
</script>
openModal(ConfirmDialog, {
  group: 'confirm',
  props: {
    title: 'Delete?',     // ✓ required string
    message: 'Sure?',     // ✓ optional string
    unknown: true         // ✗ Type error
  }
})

Declaring a component's group

Use defineOptions to bind a component to a group at design time:

ConfirmDialog.vue
<script setup lang="ts">
defineOptions({ modalGroup: 'confirm' })
</script>

After declaring modalGroup, openModal(ConfirmDialog) without a group option is valid. Passing an undeclared group value is still a type error.

useModal type inference

const modal = useModal<SaveResult>(SaveDialog, {
  group: 'default'
})

await modal.open({ props: { title: 'Save' } })

The second generic (C) is usually inferred — you only need it if TypeScript cannot resolve the component type from context.

ModalHandle typing

ModalHandle<T> extends Promise<T>:

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
}

Store it typed when you need to close it later:

const handle = openModal<boolean>(ConfirmDialog, { group: 'confirm' })
handle.close({ success: true, data: true })

Full registry pattern example

src/modal-types.ts
import { type DefineGroups } from '@kolirt/vue-modal'

declare module '@kolirt/vue-modal' {
  interface ModalGroupRegistry extends DefineGroups<[
    'default',
    'confirm',
    'panel'
  ]> {}
}
src/main.ts
import './modal-types'   // side-effect import to activate augmentation
import { createModal } from '@kolirt/vue-modal'

app.use(createModal({
  groups: {
    default: {},
    confirm: { disableCloseOnEscape: true },
    panel:   { enableInteractOutside: true, disableLockBodyScroll: true }
  }
}))

CloseModalOptions<T> typing

When calling closeModalById with success: true the data field must match T:

closeModalById<boolean>(handle.id, { success: true, data: true })   // ✓
closeModalById<boolean>(handle.id, { success: true, data: 'oops' }) // ✗
Copyright © 2026