@anfo/nuxt-dialogs-plugin
v1.0.2
Published
Nuxt module that auto-scans a directory for Vue dialog/drawer components and exposes them through a fully type-safe virtual module (virtual:dialogs). Dialog apps automatically inherit the Nuxt app context (Pinia, i18n, UI libraries, global components).
Maintainers
Readme
@anfo/nuxt-dialogs-plugin
A Nuxt module that auto-scans a directory for Vue dialog/drawer components and exposes them through a fully type-safe virtual module (virtual:dialogs). Dialogs are mounted programmatically into isolated Vue app instances — no <Teleport> boilerplate, no global store, automatic cleanup on close.
Nuxt-native extras — dialog apps automatically inherit the Nuxt app's context, so Pinia, i18n, UI libraries, global components and directives work inside dialogs with zero wiring. dialogs and the runtime composables are auto-imported.
This is the Nuxt counterpart of
@anfo/vite-dialogs-plugin— it is a standalone package and does not depend on it.
Installation
npm install -D @anfo/nuxt-dialogs-pluginQuick Start
1. Register the module — nuxt.config.ts
export default defineNuxtConfig({
modules: ["@anfo/nuxt-dialogs-plugin"],
// Optional — these are the defaults
dialogs: {
dir: "dialogs", // relative to srcDir
// pattern: /(Dialog|Drawer)\.vue$/,
// inheritNuxtApp: true,
},
});2. Create a dialog component
Every .vue file whose name ends with Dialog.vue or Drawer.vue inside your dir becomes a key on the dialogs object. No tsconfig changes are needed — the module generates the type declarations for you.
<!-- dialogs/ConfirmDialog.vue -->
<script setup lang="ts">
import {
useDialogContext,
createDialogExpose,
type DialogExposed,
} from "@anfo/nuxt-dialogs-plugin/runtime";
defineProps<{ message: string }>();
const { resolve, reject } = useDialogContext<boolean>();
defineExpose<DialogExposed<boolean>>(createDialogExpose<boolean>());
</script>
<template>
<div class="overlay">
<div class="box">
<p>{{ message }}</p>
<button @click="resolve(true)">Confirm</button>
<button @click="resolve(false)">Cancel</button>
</div>
</div>
</template>3. Open dialogs from anywhere
dialogs is auto-imported — no import statement needed:
<script setup lang="ts">
const result = await dialogs.ConfirmDialog({ message: "Delete this item?" });
if (result.type === "resolve" && result.value) {
// confirmed
}
// Or chain callbacks
dialogs
.ConfirmDialog({ message: "Are you sure?" })
.resolve((value) => console.log("resolved:", value))
.reject((reason) => console.log("rejected:", reason));
</script>Context inheritance — why dialogs "just work"
Each dialog is mounted into its own isolated createApp() instance. By default (inheritNuxtApp: true) the module captures the Nuxt app and clones its context — global components, directives and provides — onto every dialog app. That means everything your Nuxt app registered is available inside dialogs automatically:
- Pinia stores (
@pinia/nuxt) - i18n (
@nuxtjs/i18n—$t, translations) - UI libraries (Element Plus, PrimeVue, Vuetify… registered via Nuxt plugins)
- Global components and directives
- The
nuxtAppitself —useNuxtApp(),useRoute()… work inside dialogs
Set inheritNuxtApp: false to keep dialogs fully isolated (parity with plain Vite projects), and register exactly what you need with configureDialogs():
// plugins/dialogs.ts
export default defineNuxtPlugin((nuxtApp) => {
configureDialogs({
use: [
(app) => {
app.config.globalProperties.$something = "...";
},
],
});
});Both plugin objects ({ install(app) {} }) and plain (app) => void functions are accepted.
Module Options
| Option | Type | Default | Description |
|---|---|---|---|
| dir | string | "dialogs" | Directory containing dialog components; relative paths resolve against srcDir. |
| pattern | RegExp | /(Dialog\|Drawer)\.vue$/ | RegExp to identify which files are dialogs. |
| inheritNuxtApp | boolean | true | Dialog apps inherit the Nuxt app's context (components, directives, provides). |
Auto-imports
| Name | From | Description |
|---|---|---|
| dialogs | virtual:dialogs | Open dialogs — dialogs.ConfirmDialog(props). |
| useDialogContext | ./runtime | Get { resolve, reject } inside a dialog component. |
| createDialogExpose | ./runtime | Typed expose helper for defineExpose. |
| configureDialogs | ./runtime | Register extra plugins for every dialog app. |
Package Exports
| Specifier | Contents |
|---|---|
| @anfo/nuxt-dialogs-plugin | Nuxt module |
| @anfo/nuxt-dialogs-plugin/runtime | Types, useDialogContext, createDialogExpose, configureDialogs, DialogExposed |
SSR
Dialogs are DOM-only. Calling dialogs.* during server-side rendering is safe — it warns and settles as { type: "reject", reason } instead of crashing the render. Open dialogs from event handlers or onMounted.
Do not import
virtual:dialogsfrom Nitro server code (server/api,server/routes) — the virtual module is only available in the app build.
Project Structure
<srcDir>/
├── dialogs/ ← your dialog components (configurable)
│ ├── ConfirmDialog.vue
│ ├── AlertDialog.vue
│ └── UserDrawer.vue
└── nuxt.config.tsHow It Works
- On startup the module registers a Vite plugin, a tiny runtime plugin (context capture), auto-imports and a type template.
- The Vite plugin generates a virtual module (
virtual:dialogs) that imports each component through the#dialogs-componentsalias and wraps it inmountDialog(). mountDialog()creates an isolatedcreateApp()instance per call, inherits the Nuxt app context, provides aDialogController, mounts it into a temporary<div>, and returns a Promise-based handle.- When the component calls
resolve(value)orreject(reason), the app is unmounted and the host element removed automatically. - A
.d.tstype template is written into.nuxt/so everydialogs.*entry is typed end-to-end — props, return value, and callbacks. It regenerates when dialog files are added or removed during dev.
See SKILL.md for step-by-step examples covering all dialog patterns.
License
MIT
