Async components
Async components
Pass defineAsyncComponent(() => import('./Heavy.vue')) to openModal or useModal. Vue resolves the loader on first render, after the modal is already in the stack.
Basic usage
import { defineAsyncComponent } from 'vue'
import { openModal } from '@kolirt/vue-modal'
const HeavyEditor = defineAsyncComponent(() => import('./HeavyEditor.vue'))
const result = await openModal<{ saved: boolean }>(HeavyEditor, {
group: 'default',
props: { documentId: 42 }
}).catch(() => null)
The HeavyEditor bundle is fetched only when openModal runs.
Loading and error states
defineAsyncComponent accepts a full options object:
const HeavyEditor = defineAsyncComponent({
loader: () => import('./HeavyEditor.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorCard,
delay: 200,
timeout: 5000
})
These render in place of the modal content while the bundle loads.
Define once, outside the composable
Create the async component at module scope so its reference is stable across re-renders:
// module level — created once
const ReportModal = defineAsyncComponent(() => import('./ReportModal.vue'))
// inside setup()
const report = useModal<void>(ReportModal, { group: 'default' })
Calling defineAsyncComponent inside setup or an event handler creates a new loader each time, defeating bundle deduplication.
defineOptions({ modalGroup }) is not picked up
When you open a regular (synchronous) component, the package reads defineOptions({ modalGroup }) off the component and uses it as the default group — so callers don't have to pass group every time:
<script setup lang="ts">
defineOptions({ modalGroup: 'default' })
</script>
// Sync component — group is inferred from defineOptions
await openModal(HeavyEditor, { props: { documentId: 42 } })
This does not work with defineAsyncComponent. The group lookup runs synchronously inside openModal, before the async loader has resolved — at that moment the wrapper component has no modalGroup field because the inner component hasn't been imported yet. The call throws:
[@kolirt/vue-modal] openModal() requires a `group` option
(or `defineOptions({ modalGroup: ... })` on the component).
The same applies to replaceModal() and useModal().
Always pass group explicitly
const HeavyEditor = defineAsyncComponent(() => import('./HeavyEditor.vue'))
// ✅ group passed explicitly
await openModal(HeavyEditor, {
group: 'default',
props: { documentId: 42 }
})
// ✅ same rule for useModal
const editor = useModal(HeavyEditor, { group: 'default' })
// ❌ throws — modalGroup inside HeavyEditor.vue is unreachable here
await openModal(HeavyEditor, { props: { documentId: 42 } })
Keep defineOptions({ modalGroup }) on the inner component anyway — it documents the intended group and works if you ever switch back to a sync import.
