Resources

Comparison

How @kolirt/vue-modal compares to other dialog solutions for Vue 3.

Comparison

Feature matrix

Feature@kolirt/vue-modal v2vue-final-modalHeadless UI DialogVuetify / PrimeVueNative <dialog>
Imperative openModal() APIpartial
Promise-based result
Modal groups / named targets
TypeScript-safe groupsn/an/a
Stack managementpartial
Scroll lock with shift compensation
Focus trap✓ (via reka-ui)✓ (modern browsers)
ARIA role="dialog"
Headless (bring your own CSS)✗ (opinionated)
beforeClose guardspartial
Async component supportvariesn/a
SSR-safe (no open during hydration)✓ (with care)✓ (with care)limited
Vue 2 support✓ (v2 version)variesn/a
Bundle sizesmall + reka-ui peersmallpart of @headlessui/vuelarge0

Headless UI Dialog

Headless UI provides a <Dialog> component driven by v-model. It handles focus trap and ARIA correctly and has zero styles.

Where it differs. There is no imperative API — you maintain isOpen ref and co-locate the dialog in the same template as the trigger. Each dialog is an isolated unit with no cross-dialog coordination. Opening a confirmation on top of a form modal requires wiring the state yourself. There is no promise-based result; you use callbacks or watchers.

Choose Headless UI when you have one or two dialogs per page and prefer a template-first, v-model-style API.


vue-final-modal

vue-final-modal v4+ is close in spirit: imperative useModal() composable, promise-based open/close, headless. It has a large user base and good documentation.

Where it differs. It has no concept of named groups — all modals share one global layer. There is no TypeScript augmentation for group names. The scroll-lock implementation and focus management are provided by the package itself rather than delegating to a primitives library. Transitions are configured via component props rather than CSS. It also supports Vue 2 via a separate package.

Choose vue-final-modal if you need Vue 2 support or prefer an approach without a primitives peer dep.


Vuetify / PrimeVue dialogs

Both design systems ship dialog components tightly coupled to their visual language. They include styles, transitions, themed overlays, and slot APIs integrated with the rest of the kit.

Where they differ. You cannot use these dialogs without adopting the full design system (or significant CSS overrides). The API is template-first (v-model / event callbacks). Imperative usage is possible via globally injected methods in some versions, but is not the primary pattern. Stack management and group semantics are absent.

Choose these when your app is already on Vuetify or PrimeVue and you want dialogs that match your component library out of the box without any styling work.


Native <dialog>

The browser's <dialog> element with .showModal() provides a focus trap, Escape to close, and ::backdrop styling — no library needed.

Where it differs. No transitions (CSS @starting-style is still limited). No return value — you listen to the close event and read dialog.returnValue. No stacking protocol; nesting multiple dialogs requires manual z-index management. No scroll lock on older browsers.

Choose native <dialog> for a single simple modal in a project with minimal tooling requirements.


When to choose @kolirt/vue-modal

This library is a good fit when:

  • Your app has many dialogs distributed across pages and components (not co-located with their triggers).
  • Flows chain modals — a form opens a confirm, which opens a sub-dialog — and you want each await openModal(…) to stay linear.
  • TypeScript correctness on every modal call matters to your team.
  • You need independently styled and positioned modal stacks (group A at center, group B as a side drawer).
  • You want full control over markup and animation without fighting a framework's defaults.

It is probably not the right choice when you want a modal that closely mirrors a design system's visual style (use that system's dialog), or when your project has only one or two simple overlays (native <dialog> or v-if is sufficient).

Copyright © 2026