Dialog
Accessible, unstyled modal Dialog compound primitive.
|
|
| Package |
@vizejs/ui/dialog |
| Maturity |
stable |
| Own the source |
vize lib pull dialog |
| Requires |
context, controllable-state, dismissable-layer, focus-guards, focus-scope, id, inert-outside, portal, scroll-lock |
| Aliases |
modal, alert dialog, sheet foundation, overlay dialog |
| Covers |
WAI-ARIA dialog pattern, Radix Dialog, Reka UI Dialog |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Dialog, DialogRoot, DialogClose, DialogContent, DialogDescription, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from "@vizejs/ui/dialog";
Or copy the source into your project with vize lib pull dialog (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components

Copy the example
<!-- Modal dialog opened from a trigger, labelled by its title, and closed from inside. -->
<script setup lang="ts">
import { ref } from "vue";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
} from "@vizejs/ui/dialog";
const open = ref(false);
</script>
<template>
<Dialog v-model:open="open">
<DialogTrigger>Edit profile</DialogTrigger>
<DialogPortal>
<DialogOverlay />
<DialogContent>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Update how your name appears to teammates.</DialogDescription>
<label>
Display name
<input name="displayName" autocomplete="nickname" />
</label>
<DialogClose>Save changes</DialogClose>
</DialogContent>
</DialogPortal>
</Dialog>
</template>
API
Dialog
Source: src/families/overlays/dialog/dialog-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Dialog base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Whether the dialog makes outside content inert, focus-contained, and scroll-locked. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Dialog requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
exit-complete |
[] |
Fired once when an open dialog has fully closed, including without exit motion. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState |
Compound Dialog children. Receives the current open and modal state. |
Exposed
| Member |
Type |
Description |
close |
— |
|
contentId |
— |
|
descriptionId |
— |
|
id |
— |
|
modal |
— |
|
open |
— |
|
openDialog |
— |
|
setOpen |
— |
|
state |
— |
|
titleId |
— |
|
toggle |
— |
|
DialogRoot
Source: src/families/overlays/dialog/dialog-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Dialog base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
true |
Whether the dialog makes outside content inert, focus-contained, and scroll-locked. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Dialog requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
exit-complete |
[] |
Fired once when an open dialog has fully closed, including without exit motion. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState |
Compound Dialog children. Receives the current open and modal state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the dialog content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert, focus-contained, and scroll-locked. |
state |
DialogState |
Stable state token for styling and tests. |
id |
string |
Root-owned base id for the compound dialog family. |
contentId |
string |
Id wired from DialogTrigger to DialogContent. |
titleId |
string |
Default id consumed by DialogTitle. |
descriptionId |
string |
Default id consumed by DialogDescription. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request a specific open value and report whether it differs. |
openDialog |
(event?: Event | null) => boolean |
Request the open state. |
close |
(event?: Event | null) => boolean |
Request the closed state. |
toggle |
(event?: Event | null) => boolean |
Request the opposite open state. |
DialogClose
Source: src/families/overlays/dialog/dialog-close.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove the close button from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the button requests closing. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState & { readonly disabled: boolean } |
Close button contents. Receives the current Dialog state and button availability. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the close button. |
DialogContent
Source: src/families/overlays/dialog/dialog-content.vue
Props
| Prop |
Type |
Default |
Description |
role |
DialogRole |
"dialog" |
Dialog role announced by assistive technology. |
forceMount |
boolean |
false |
Keep content mounted while closed. |
trapFocus |
boolean |
true |
Contain focus inside an open modal dialog. |
autoFocus |
boolean |
true |
Move focus into content when it opens. |
restoreFocus |
boolean |
true |
Restore focus when content closes. |
inertOutside |
boolean |
true |
Make outside content inert while the modal dialog is open. |
lockScroll |
boolean |
true |
Lock document scroll while the modal dialog is open. |
closeOnEscape |
boolean |
true |
Let Escape request dismissal. |
closeOnPointerDownOutside |
boolean |
true |
Let outside pointer-down request dismissal. |
closeOnFocusOutside |
boolean |
true |
Let outside focus movement request dismissal. |
ariaLabel |
string |
undefined |
Accessible name when no visible title supplies one. |
ariaLabelledby |
string | null |
undefined |
Space-separated ids that label the dialog. null omits the default title id. |
ariaDescribedby |
string | null |
undefined |
Space-separated ids that describe the dialog. null omits the default description id. |
Events
| Event |
Payload |
Description |
open-auto-focus |
[event: DialogAutoFocusEvent] |
Fired before automatic entry focus is applied. |
close-auto-focus |
[event: DialogAutoFocusEvent] |
Fired before automatic focus restoration is applied. |
escape-key-down |
[event: DismissableLayerEscapeKeyDownEvent] |
Fired before Escape requests dismissal. |
pointer-down-outside |
[event: DismissableLayerPointerDownOutsideEvent] |
Fired before an outside pointer-down requests dismissal. |
focus-outside |
[event: DismissableLayerFocusOutsideEvent] |
Fired before outside focus movement requests dismissal. |
interact-outside |
[event: DismissableLayerInteractOutsideEvent] |
Fired before outside pointer or focus interaction requests dismissal. |
dismiss |
[event: DismissableLayerDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState |
Dialog content. Receives the current open and modal state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the dialog content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert, focus-contained, and scroll-locked. |
state |
DialogState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered dialog content element. |
focusFirst |
() => HTMLElement | null |
Focus the first eligible descendant in the dialog content. |
focusContent |
(options?: FocusOptions) => void |
Move focus to the content fallback target. |
DialogDescription
Source: src/families/overlays/dialog/dialog-description.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned description id. null and undefined use the Dialog default. |
as |
PrimitiveAs |
"p" |
Native element, custom element, or component to render. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Visible Dialog description. |
Exposed
| Member |
Type |
Description |
element |
Element | null |
Rendered description element or component instance. |
DialogOverlay
Source: src/families/overlays/dialog/dialog-overlay.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the overlay mounted while the dialog is closed. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState |
Optional overlay contents. Receives the current Dialog state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the dialog content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert, focus-contained, and scroll-locked. |
state |
DialogState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered overlay element. |
DialogPortal
Source: src/families/overlays/dialog/dialog-portal.vue
Props
| Prop |
Type |
Default |
Description |
to |
string | HTMLElement |
"body" |
CSS selector or element the dialog layer is moved into. |
disabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
forceMount |
boolean |
false |
Keep the portal host mounted while the dialog is closed. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState |
Portalled Dialog layer contents. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the dialog content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert, focus-contained, and scroll-locked. |
state |
DialogState |
Stable state token for styling and tests. |
present |
boolean |
Whether portal contents are rendered. |
DialogTitle
Source: src/families/overlays/dialog/dialog-title.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned title id. null and undefined use the Dialog default. |
as |
PrimitiveAs |
"h2" |
Native element, custom element, or component to render. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Visible Dialog title. |
Exposed
| Member |
Type |
Description |
element |
Element | null |
Rendered title element or component instance. |
DialogTrigger
Source: src/families/overlays/dialog/dialog-trigger.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove the trigger from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the trigger requests opening. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
DialogSlotState & { readonly disabled: boolean } |
Trigger contents. Receives the current Dialog state and trigger availability. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
Behavior
| Surface |
Contract |
| State |
DialogRoot supports uncontrolled defaultOpen and controlled open with update:open and open-change events. |
| Trigger |
DialogTrigger renders a native button with aria-haspopup="dialog", aria-expanded, and aria-controls wired to content. |
| Portal |
DialogPortal composes Portal, defers teleport until hydration, and can stay mounted with forceMount. |
| Modal layer |
DialogContent uses dismissable layer, focus scope, focus guards, inert outside, and scroll lock while open and modal. |
| Dismissal |
Escape, outside pointer-down, outside focus, and close button request closing unless the preventable callback was canceled. |
| Labelling |
DialogTitle and DialogDescription consume deterministic ids generated by DialogRoot; content may override ARIA ids or label, and null omits the default id. |
| Focus |
Open content auto-focuses the first eligible descendant or the content fallback and restores focus to the trigger on close. |
| SSR |
Server output is deterministic, renders in place through Portal, and activates document controllers only after mount. |
| Composition |
dialogContext is exported so composed families (Drawer) provide the Dialog contract and reuse trigger, title, description, and close parts. |
| Styling |
JavaScript remains headless. Optional @vizejs/ui/component-dialog.css styles the published data-vize-ui, part, and data-state hooks. |
| Exit |
exit-complete fires once after an open dialog closes, including when CSS motion is absent or reduced. Reopening cancels the pending exit. |
| Component |
State x input |
Outcome |
dialog-root.vue |
controlled or uncontrolled open request |
Publishes open, modal, ids, and data-state; emits update:open and open-change only for distinct requests. |
dialog-trigger.vue |
enabled click |
Emits click, then opens the root when the event was not canceled. |
dialog-portal.vue |
open or forceMount |
Renders Portal; otherwise leaves only a hidden host. |
dialog-overlay.vue |
open or forceMount |
Renders an inert-safe backdrop branch with aria-hidden="true". |
dialog-content.vue |
open modal |
Activates dismissal, focus containment, focus guards, inert outside, and scroll lock. |
dialog-title.vue |
no explicit id |
Uses the root deterministic title id for aria-labelledby. |
dialog-description.vue |
no explicit id |
Uses the root deterministic description id for aria-describedby. |
dialog-close.vue |
enabled click |
Emits click, then closes the root when the event was not canceled. |
| Event |
Dispatch timing and payload |
open-auto-focus |
Before automatic entry focus; payload is a preventable focus-scope event. |
close-auto-focus |
Before automatic focus restoration; payload is a preventable focus-scope event. |
escape-key-down |
Before Escape dismissal; payload is the preventable dismissable-layer Escape event. |
pointer-down-outside |
Before pointer dismissal; payload is the preventable outside pointer event. |
focus-outside |
Before focus dismissal; payload is the preventable outside focus event. |
interact-outside |
Before pointer or focus dismissal; payload is the preventable outside interaction event. |
dismiss |
After an unprevented dismissal request; payload records the reason, target, and native event. |
exit-complete |
After an open dialog's visual exit finishes or is skipped; no payload. Initial closed state does not dispatch it. |