Resources

Troubleshooting

Common errors and problems in @kolirt/vue-modal v2, with causes and fixes.

Troubleshooting

Error messages

[@kolirt/vue-modal] openModal() requires a 'group' option…

Cause. Neither options.group was passed to openModal / replaceModal, nor does the component carry defineOptions({ modalGroup: '…' }).

Fix. Add the group explicitly:

openModal(MyModal, { group: 'default' })

or declare it on the component so it can be omitted at call sites:

// MyModal.vue
defineOptions({ modalGroup: 'default' })

[@kolirt/vue-modal] <ModalRoot> must be used inside a modal opened via openModal/useModal

Cause. <ModalRoot> reads modalContextKey from inject. If the component is rendered outside the package's internal mount path (e.g., dropped directly in a template without going through openModal), the injection is absent.

Fix. Only render <ModalRoot> inside components that are opened via openModal or useModal. Do not place it in static templates.


[@kolirt/vue-modal] <ModalRoot> must be rendered inside a <ModalTarget> tree

Cause. The modal was opened for a group that has no mounted <ModalTarget>. <ModalRoot> looks for modalGroupConfigKey which <ModalTarget> provides.

Fix. Add a <ModalTarget> for that group somewhere in your app layout:

<ModalTarget group="default" />

Confirm the target is actually mounted (not hidden behind a v-if) when the modal opens.


[@kolirt/vue-modal] <ModalContent> must be used inside <ModalRoot>

Cause. <ModalContent> injects modalRootContextKey which <ModalRoot> provides. Using <ModalContent> outside <ModalRoot>, or swapping the nesting order, breaks the injection.

Fix. Ensure the hierarchy is <ModalRoot><ModalContent> with nothing between them that breaks the provide chain (e.g., async boundaries).


[@kolirt/vue-modal] useModalContext() must be called inside a modal component

Cause. useModalContext was called in a component that was not opened through the package (no modalContextKey injection present).

Fix. Call useModalContext only inside components opened via openModal or useModal.


Behavioral problems

Most likely <ModalContent> is missing from the modal component. <ModalRoot> renders a <DialogRoot> wrapper; visible content and transitions live inside <ModalContent>. Without it, nothing is shown and the exit animation never fires, leaving the modal stuck in the stack.

The exit animation never completed, so finalizeModal was never called. This happens when:

  • <ModalContent> is absent (its onAfterLeave hook triggers finalize).
  • A CSS transition is defined but the transitionend event never fires (e.g., display: none applied instead of opacity).

Check that <ModalContent> is present and that any transitions end normally.

z-index stacking issues

<ModalTarget> renders a div[data-modal-region] with position: fixed; inset: 0 at zero specificity (:where()). If another fixed element on the page has a higher stacking context, it may overlay the modal.

Options:

  • Move <ModalTarget> later in the DOM (later siblings paint on top in the same stacking context).
  • Set an explicit z-index by targeting [data-modal-region] in your CSS.
  • Wrap <ModalTarget> in a <div style="position: fixed; inset: 0; z-index: 1000">.

Body scroll-lock causes layout shift

When scroll lock engages, the scrollbar disappears and content shifts right. useScrollLock automatically compensates by adding paddingRight equal to the scrollbar width. If you still see a shift:

  • Ensure no other code resets body.paddingRight.
  • If your layout uses overflow: hidden on a wrapper instead of <body>, the padding compensation may apply to the wrong element — target [data-modal-region] with a matching padding instead.

When a modal is opened and closed faster than its enter animation completes, the enter and exit can visually collide.

Use instantEnter or instantExit flags to skip animations for programmatic flows:

openModal(MyModal, { group: 'default', instantEnter: true })
// or close immediately:
handle.close({ instantExit: true })

TypeScript errors

Type '"foo"' is not assignable to type 'never' on group

Cause. ModalGroupRegistry has no entries, so ModalGroup resolves to never.

Fix. Declare your groups in a .d.ts file included by tsconfig.json:

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

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

Props type error on openModal

openModal infers prop types from the component's $props. If props are not typed with defineProps<{…}>(), inference falls back to Record<string, unknown>. Add explicit prop types to the modal component to get autocomplete and type checking on options.props.

Copyright © 2026