Open modals from any function, stack them as needed, and style them however you want.
No template boilerplate, no manual state — just call and await.
📚 Documentation · 🎮 Playground · 🍳 Recipes
@kolirt/vue-modal is a lightweight, headless modal package for Vue 3. It lets you open, stack, and control dialogs imperatively from any function — without registering modal components in templates or wiring open/close state by hand.
- Open from JS/TS — trigger modals from any function and await the user's response. A single call returns a typed promise with full TypeScript inference for props and result.
- Less template boilerplate — skip placing every modal in your templates and wiring open/close state by hand. Register one mount point and trigger any modal from code with a single call.
- Cascading modals — open multiple modals one after another while preserving their state and context. Layer a confirmation on top of a form without losing the form's data.
- Highly customizable — headless primitives with no imposed styles. Bring your own CSS, transitions, and animations — compose modals that fit any design system.
- Modal groups — isolate flows with named groups — the main app stack, confirm dialogs, side panels — each rendering in its own mount point with its own queue.
- Async components — open any Vue component, including async ones loaded on demand. Heavy modals stay out of your initial bundle and resolve through the same promise.
npm install @kolirt/vue-modal reka-ui
# or
yarn add @kolirt/vue-modal reka-ui
# or
pnpm add @kolirt/vue-modal reka-uireka-ui is a peer dependency.
1. Register groups and install the plugin (main.ts):
The package requires every modal to belong to a registered group. Without registered groups the package can't be used at all — there's no implicit 'default'.
import { createApp } from 'vue'
import { createModal, type DefineGroups } from '@kolirt/vue-modal'
import App from './App.vue'
// (TypeScript only) Type-check every `group` reference against this list.
declare module '@kolirt/vue-modal' {
interface ModalGroupRegistry extends DefineGroups<['default']> {}
}
const app = createApp(App)
app.use(
createModal({
groups: {
// per-group behavior options — see /guide/behavior-options for the full list
default: {}
}
})
)
app.mount('#app')2. Mount a <ModalTarget> for each group (App.vue):
<script setup lang="ts">
import { ModalTarget, ModalOverlay } from '@kolirt/vue-modal'
</script>
<template>
<RouterView />
<ModalTarget group="default">
<ModalOverlay class="overlay" />
</ModalTarget>
</template>3. Write a modal:
<!-- ConfirmDialog.vue -->
<script setup lang="ts">
import { ModalRoot, ModalContent, ModalTitle, ModalDescription, useModalContext } from '@kolirt/vue-modal'
defineOptions({ modalGroup: 'default' })
const props = defineProps<{ message: string }>()
const { close, confirm } = useModalContext<boolean>()
</script>
<template>
<ModalRoot class="root">
<ModalContent class="card">
<ModalTitle>Confirm</ModalTitle>
<ModalDescription>{{ props.message }}</ModalDescription>
<div class="actions">
<button @click="close()" class="btn btn--cancel">Cancel</button>
<button @click="confirm(true)" class="btn btn--confirm">OK</button>
</div>
</ModalContent>
</ModalRoot>
</template>Styles omitted for brevity. See the first modal page for the full version with enter/exit animations.
4. Open it from anywhere:
import { openModal } from '@kolirt/vue-modal'
import ConfirmDialog from './ConfirmDialog.vue'
const ok = await openModal<boolean>(ConfirmDialog, {
props: { message: 'Delete this project?' }
}).catch(() => false)
if (ok) {
// user pressed OK
}Everything lives at kolirt.github.io/vue-modal:
- Getting started — install, setup, your first modal
- Concepts — architecture, imperative flow, stacking, groups, headless primitives
- Guide — writing modals, props & results, behavior options, styling,
useModal, multiple targets, async components, TypeScript - Recipes — confirm dialog, form with validation, lightbox, command palette, nested flows, global error modal
- API reference — functions, components, composables, plugin, state, types
- Migration from v1 · FAQ · Troubleshooting · Changelog
Check out my other projects on my GitHub profile.