Dialogs are not mounted where they are used. Each app has one host that renders whichever dialog is open, and anything that wants a dialog just asks for it by name. This replaces the usual pattern of an isOpen ref and a dialog tag sitting in every parent that can trigger one.
The standard approach is fine for a single dialog on a single page, but it falls apart quickly. The same dialog triggered from a sidebar and a slide-out menu means the same refs, handlers, and mounts written twice. A dialog that edits an item needs a selected item ref living next to it. And the dialog component stays mounted between opens while its content does not, which is where the real trouble starts.
reka-ui unmounts dialog content when it closes, and vee-validate drops a field's value when the field unmounts. Put a form in a dialog that stays mounted and the second open shows an empty input, backed by a form that thinks the field was never filled in. You can patch it with a reset on every open, but that is boilerplate in every form dialog for a problem that should not exist in the first place.
The manager sidesteps all of that by mounting a dialog fresh on every open and unmounting it once it has closed. A form dialog builds its form from the props it was handed each time, so there is nothing to reset and no stale values or errors carried over from last time.
Closing happens in two steps so the animation still plays. The dialog is hidden first, then removed after a short delay. That delay is CLOSE_DELAY in the dialog service, set to 250ms to outlast the duration-200 close animation on the dialog and alert dialog primitives. If you change those animations, change the delay with them, otherwise dialogs get cut off mid-fade.
Only one dialog is ever open. Asking for a new one while another is showing closes the current one, waits out the delay, and then opens the next, so moving from one dialog to another transitions cleanly instead of stacking.
Each app lists its dialogs in src/config/dialogs.ts, a plain map of names to components. Shared code picks it up through the @ alias, the same way it reads each app's config/settings.ts, so the host and service live in shared/ while the list of dialogs stays per app. An app with no dialogs still needs the file with an empty map, since the shared code imports it either way.
The props for each dialog are read straight off its own defineProps, so opening a dialog by name autocompletes and type checks its props without a second type to keep in sync. Adding a dialog comes down to creating the component, adding a line to the registry, and calling open wherever it is needed.
The state sits in a Pinia store behind useDialogService, and the host is a shared feature mounted once in each app's App.vue.
Opening a dialog closes any open SheetMenu. Dialogs are often triggered from inside a slide-out menu, and leaving the menu open underneath is never what you want, so it is handled once in the menu rather than at every trigger.
Mobile for the slide-out menus. Component Architecture for where the host and service sit in the component layers. Settings for the per-app config the registry sits alongside.