@korneevecin/vue3-dialogs-lib
| Install | |
|---|---|
npm install @korneevecin/vue3-dialogs-lib |
|
| Latest Version: | 0.6.2 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 9, 2026 |
| Links: | GitHub · npm |
@korneevecin/vue3-dialogs-lib
English · Русский
A window manager for Vue 3, built on the native <dialog> element, with no runtime dependency
beyond Vue itself.
A window here is a serializable descriptor in a store, not a boolean owned by the component that opened it. So a window outlives its opener: it can be minimized and picked up again from anywhere in the app, it remembers where it was on screen, it costs nothing while minimized (its content is unmounted, not hidden), and with persistence on it survives a page reload.
Features
- Native
<dialog>, non-modal by default — several windows at once, the page behind stays usable, teleported poppers from your own components keep working. - Minimize = unmount. A minimized window keeps its descriptor and draft state, and nothing else.
- Drag, resize from eight grips, keyboard nudging, min/max size limits, viewport clamping, fullscreen below a mobile breakpoint.
- Edge snapping like a desktop: halves, quarters, maximize (with an opt-in button), a ghost
preview, and a rebindable keymap for snapping and window switching.
tileAll()andcascadeAll()arrange every window at once. - Persistence to any
{ getItem, setItem, removeItem }storage, with a schema check, repair of partial descriptors and a guard against two tabs writing the same key. - Close guards — app-wide and per window, sync or async — behind
requestClose(). - Window results:
open()returns a promise for what the window settled with, and it never hangs. - Owned child windows, modal windows with a scrim and an
inertsweep, pinned (always-on-top) windows, and named presets. - A title-bar context menu slot: right-click or
Shift+F10on a header opens your menu, wired to snap, maximize, pin, minimize and close. requestAttention(id)— a window asks for the user without stealing focus; the frame and the taskbar slot both see it, and it clears when the window becomes active.- Typed
open()— window names and props checked against your components map. - Events with payloads —
on()hands over the rect, the close reason and result, the last descriptor, pin, snap, active-window and props/meta changes. - Async loading and error states per window type, with failure contained to one window.
- Works without a stylesheet. Layout is inline;
style.cssis cosmetics, restyled through--vtd-*tokens, plus 22 ready-made colour themes. - SSR-safe, zero runtime dependencies, no user-facing strings baked in.
Install
pnpm add @korneevecin/vue3-dialogs-lib
# or: npm install @korneevecin/vue3-dialogs-lib
Vue 3.5 or newer is the only requirement (a peer dependency). The stylesheet is optional: without it the windows are unstyled but fully functional.
Quick start
Register your window components once and mount the host above the router outlet:
// main.js
import { createWindows } from '@korneevecin/vue3-dialogs-lib'
import '@korneevecin/vue3-dialogs-lib/style.css' // optional
app.use(createWindows({
components: {
itemEditor: () => import('./windows/ItemEditor.vue'),
logViewer: () => import('./windows/LogViewer.vue'),
},
persist: { key: 'app:windows', storage: localStorage }, // omit to disable
labels: { minimize: 'Minimize', close: 'Close', pin: 'Keep on top' },
}))
<!-- App.vue -->
<router-view />
<WindowHost />
<WindowTaskbar v-slot="{ all, active, restore, focus, close }">
<button
v-for="w in all"
:key="w.id"
:class="{ active: w.id === active }"
@click="w.minimized ? restore(w.id) : focus(w.id)"
>
{{ w.title }}
<span @click.stop="close(w.id)">✕</span>
</button>
</WindowTaskbar>
WindowTaskbar renders no markup of its own — the visual is entirely yours.
Open windows from anywhere, inside or outside setup():
import { useWindows } from '@korneevecin/vue3-dialogs-lib'
const win = useWindows()
const { id, result } = win.open('itemEditor', { id: 42 }, { title: 'Item 42', w: 720, h: 520 })
win.minimize(id)
win.restore(id)
await win.requestClose(id) // runs the close guards; false if one refused
const saved = await result // { ok: true, data } | { ok: false, reason: 'closed' | 'restored' }
Outside setup() there is nothing to inject from, so useWindows() falls back to the store of the
most recently installed app. That fallback is shared across concurrent SSR requests, so on a server
take the store inside a component instead.
Inside a window's content:
<script setup>
import { useWindowState, useWindowContext } from '@korneevecin/vue3-dialogs-lib'
const props = defineProps({ id: Number, windowId: String })
// draft state that survives minimize (unmount) and page reload
const form = useWindowState(props.windowId, () => ({ name: '', note: '' }))
const { setTitle, onBeforeClose, resolve, dismiss } = useWindowContext()
setTitle(`Item ${props.id}`)
onBeforeClose(() => !form.name || confirm('Discard the draft?'))
</script>
Modal question, answered by its result:
const answer = await win.open('confirm', { message }, { modal: true, placement: 'center' }).result
if (answer.ok) proceed()
Styling
Everything a window needs to work is inline, so the library runs with no stylesheet imported. Two
pieces are pure tint, though, and without style.css they are invisible: the snap ghost a drag
arms, and the scrim behind a modal — which still blocks clicks, you just cannot see it. Four lines
bring both back:
.vw-ghost { outline: 2px dashed currentColor; outline-offset: -2px; }
.vw-scrim { background: rgb(0 0 0 / 0.4); }
Reduced motion. style.css drops --vtd-motion-duration to 0ms under
prefers-reduced-motion: reduce, at zero specificity — so if you set your own duration, set it
inside a (prefers-reduced-motion: no-preference) query or check matchMedia before writing it,
or your value wins over the user's setting. Motion
has the snippet.
Documentation
| English | Русский | |
|---|---|---|
| API reference | Справочник API | every option, method and behaviour |
| Window modes | Режимы окон | plain, restricted, pinned, owned sheet, modal — with what survives a reload |
| How it works | Как это работает | the descriptor model, render path, geometry, persistence |
| Recipes | Рецепты | complete use cases with code |
| Motion | Анимация | frame lifecycle, replacing the animation, fly-to-taskbar |
| Performance | Производительность | benchmarks and what they imply |
| Build and pack | Сборка и упаковка | pnpm setup, dist/, publishing |
Non-goals
showModal() and the browser top layer, a focus-trap loop, confirm/alert helpers, data fetching or
staleness resolution, cross-device layout sync, tiling window management, and a bundled design
system. Modal windows are the narrow reading of the first two: an opt-in scrim and an inert
sweep, per window.
Development
pnpm install
pnpm dev # playground at playground/
pnpm exec vitest run # unit (jsdom) + browser (Playwright Chromium) projects
pnpm bench # benchmarks
pnpm lint && pnpm type-check
pnpm build # dist/
pnpm pack # local tarball; prepack rebuilds dist/ first
The browser test project needs Chromium once: pnpm exec playwright install chromium.
License
Related Packages
Headless, framework-agnostic window manager for the web — draggable, resizable,...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.6.2 | ^3.5.0 | MIT | |
| 0.6.1 | ^3.5.0 | MIT | |
| 0.6.0 | ^3.5.0 | MIT | |
| 0.5.0 | ^3.5.0 | MIT | |
| 0.4.0 | ^3.5.0 | MIT | |
| 0.3.0 | ^3.5.0 | MIT |