cv-dialog
Modal or non-modal dialog overlay for presenting focused content, confirmations, or alerts.
Headless: createDialog
Usage
Anatomy
<cv-dialog> (host)
├── modal=true:
└── <dialog class="portal-shell"> (top layer)
└── <div part="overlay">
└── <section part="content" role="dialog|alertdialog">
├── <header part="header">
│ ├── <h2 part="title" id="...">
│ │ └── <slot name="title">
│ ├── <p part="description" id="...">
│ │ └── <slot name="description">
│ └── <button part="header-close" aria-label="Close">
│ └── <slot name="header-close">
├── <div part="body">
│ └── <slot>
└── <footer part="footer">
└── <slot name="footer">
└── modal=false:
└── <div class="portal-shell popover-shell" popover="manual"> (top layer)
└── <div part="overlay">
└── <section part="content" role="dialog|alertdialog">
├── <header part="header">
├── <div part="body">
└── <footer part="footer">Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
open | Boolean | false | Whether the dialog is visible |
modal | Boolean | true | Enables modal behavior (focus trap, scroll lock, backdrop) |
type | String | "dialog" | ARIA role type: dialog | alertdialog |
close-on-escape | Boolean | true | Whether Escape key closes the dialog |
close-on-outside-pointer | Boolean | true | Whether clicking outside closes the dialog |
close-on-outside-focus | Boolean | true | Whether focusing outside closes the dialog |
initial-focus-id | String | — | Id of element to focus when dialog opens |
no-header | Boolean | false | Hides the header (title, description, header close button) |
Slots
| Slot | Description |
|---|---|
(default) | Dialog body content |
title | Dialog title text |
description | Description text below the title |
header-close | Icon content for the header close button (defaults to X) |
footer | Footer content (action buttons, etc.) |
CSS Parts
| Part | Element | Description |
|---|---|---|
overlay | <div> | Backdrop/overlay container |
content | <section> | Dialog content panel with role="dialog" or role="alertdialog" |
header | <header> | Header area containing title, description, and close button |
title | <h2> | Dialog title element |
description | <p> | Dialog description element |
header-close | <button> | Header close icon button |
body | <div> | Body content area |
footer | <footer> | Footer area for user-provided action buttons |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--cv-dialog-width | var(--cv-dialog-width-m, min(560px, calc(100vw - 32px))) | Preferred dialog inline size |
--cv-dialog-width-s | app/theme provided | Standard small dialog inline size |
--cv-dialog-width-m | app/theme provided | Standard medium dialog inline size |
--cv-dialog-width-l | app/theme provided | Standard large dialog inline size |
--cv-dialog-width-xl | app/theme provided | Standard extra-large dialog size |
--cv-dialog-max-height | calc(100dvh - 32px) | Maximum block size before scrolling |
--cv-dialog-header-spacing | var(--cv-space-4, 16px) | Header padding |
--cv-dialog-body-spacing | var(--cv-space-4, 16px) | Body padding |
--cv-dialog-footer-spacing | var(--cv-space-4, 16px) | Footer padding |
--cv-dialog-overlay-color | var(--cv-color-overlay) | Backdrop overlay color |
--cv-dialog-border-radius | var(--cv-radius-lg, 14px) | Panel border radius |
--cv-dialog-transition-duration | var(--cv-duration-fast, 120ms) | Presence transition duration |
--cv-dialog-transition-easing-open | var(--cv-easing-decelerate) | Opening transition easing |
--cv-dialog-transition-easing-close | var(--cv-easing-accelerate) | Closing transition easing |
--cv-dialog-content-closed-transform | translate3d(0, 8px, 0) scale(0.98) | Panel transform while closed/closing |
--cv-dialog-content-open-transform | translate3d(0, 0, 0) scale(1) | Panel transform while open |
Visual States
| Host selector | Description |
|---|---|
:host([open]) | Dialog logical state is open |
:host([modal]) | Modal mode active (focus trap, scroll lock, backdrop) |
:host([type="alertdialog"]) | Alert dialog mode with role="alertdialog" |
:host([no-header]) | Header section hidden |
The top-layer shell, overlay, and content expose data-state="closed|opening|open|closing" internally. The shell remains mounted during closing so the exit transition can finish before native dialog/popover cleanup.
Events
| Event | Detail | Description |
|---|---|---|
cv-input | {open: boolean} | Fires when open state changes via user interaction |
cv-change | {open: boolean} | Fires when open state commits |
cv-show | — | Fires when dialog begins to open |
cv-after-show | — | Fires after dialog open animation completes |
cv-hide | — | Fires when dialog begins to close |
cv-after-hide | — | Fires after dialog close animation completes |
cv-input and cv-change fire only for user-initiated close interactions (Escape, outside pointer, outside focus, header close). Programmatic open attribute changes do not emit these events.
cv-after-show and cv-after-hide fire after the presence transition completes. Reduced-motion and zero-duration paths complete immediately.
Reactive State Mapping
cv-dialog is a visual adapter over headless createDialog.
| UIKit Property | Direction | Headless Binding |
|---|---|---|
open | attr → action | actions.open() / actions.close() |
modal | attr → option | passed as isModal in createDialog(options) |
type | attr → option | passed as type in createDialog(options) |
close-on-escape | attr → option | passed as closeOnEscape in createDialog(options) |
close-on-outside-pointer | attr → option | passed as closeOnOutsidePointer in createDialog(options) |
close-on-outside-focus | attr → option | passed as closeOnOutsideFocus in createDialog(options) |
initial-focus-id | attr → option | passed as initialFocusId in createDialog(options) |
no-header | attr → DOM | controls header visibility (UIKit-only, no headless binding) |
| Headless State | Direction | DOM Reflection |
|---|---|---|
state.isOpen() | state → attr | [open] host attribute |
state.isModal() | state → attr | [modal] host attribute |
state.type() | state → attr | [type] host attribute |
state.isFocusTrapped() | state → effect | activates focus trap within the dialog |
state.shouldLockScroll() | state → effect | applies overflow: hidden to document.body |
state.restoreTargetId() | state → effect | restores focus after close when available |
state.initialFocusTargetId() | state → effect | focuses the specified element on open |
contracts.getOverlayProps()is spread onto[part="overlay"]to applyhidden,data-open, and outside pointer/focus handlers.contracts.getContentProps()is spread onto[part="content"]to applyrole(dialogoralertdialog),aria-modal,aria-labelledby,aria-describedby,tabindex, and keydown handler.contracts.getTitleProps()is spread onto[part="title"]to apply theidforaria-labelledby.contracts.getDescriptionProps()is spread onto[part="description"]to apply theidforaria-describedby.contracts.getHeaderCloseButtonProps()is spread onto[part="header-close"]to applyrole,tabindex,aria-label: 'Close', and click handler.- UIKit dispatches
cv-inputandcv-changeevents by observingisOpenchanges triggered by user interaction (not by controlledopenattribute changes). - UIKit dispatches
cv-show/cv-hideimmediately andcv-after-show/cv-after-hideafter presence transitions. - UIKit renders the visible dialog through native top-layer primitives (
<dialog>for modal, popover root for non-modal) so it is not clipped by ancestoroverflow,contain,transform, orisolation. - UIKit owns scroll lock implementation, focus trap implementation, focus restoration, backdrop rendering, and CSS transitions — headless provides signals, UIKit applies side effects.
- UIKit keeps modal scroll lock and native top-layer presence active during the close transition, then restores focus after the shell has closed.