Skip to content

cv-dialog

Modal or non-modal dialog overlay for presenting focused content, confirmations, or alerts.

Headless: createDialog

Usage

View source
html
<div class="dialog-demo-shell" data-demo="dialog" data-live-demo-height="560">
  <section class="dialog-demo-hero" aria-labelledby="dialog-demo-title">
    <div class="dialog-demo-copy">
      <span class="dialog-demo-kicker">Controlled dialogs</span>
      <h3 id="dialog-demo-title">Choose the dialog pattern by task risk</h3>
      <p>
        External controls own the open state. The labels below describe each scenario; only the action buttons
        are interactive.
      </p>
    </div>

    <div class="dialog-demo-actions" aria-label="Controlled dialog examples">
      <div class="dialog-demo-action">
        <span class="dialog-demo-label">Confirmation</span>
        <cv-button variant="primary" data-dialog-open="dialog-demo-confirm">Review transfer</cv-button>
      </div>
      <div class="dialog-demo-action">
        <span class="dialog-demo-label">Destructive alert</span>
        <cv-button variant="danger" data-dialog-open="dialog-demo-delete">Delete record</cv-button>
      </div>
      <div class="dialog-demo-action">
        <span class="dialog-demo-label">Non-modal help</span>
        <cv-button data-dialog-open="dialog-demo-info">Show details</cv-button>
      </div>
    </div>
  </section>

  <section class="dialog-demo-section" aria-labelledby="dialog-demo-variants-title">
    <div class="dialog-demo-section-header">
      <span class="dialog-demo-kicker">Variants</span>
      <h4 id="dialog-demo-variants-title">Header and close affordance examples</h4>
    </div>

    <div class="dialog-demo-grid">
      <div class="dialog-demo-panel">
        <span class="dialog-demo-label">No header</span>
        <p>Use for short acknowledgements where the body is already clear.</p>
        <cv-button data-dialog-open="dialog-demo-compact">Open compact notice</cv-button>
      </div>

      <div class="dialog-demo-panel">
        <span class="dialog-demo-label">Custom close icon</span>
        <p>Use the header-close slot when the shell needs a branded or platform-specific icon.</p>
        <cv-button data-dialog-open="dialog-demo-settings">Open settings</cv-button>
      </div>
    </div>
  </section>

  <cv-dialog
    id="dialog-demo-confirm"
    class="dialog-demo-controlled"
    initial-focus-id="dialog-demo-confirm-action"
  >
    <span slot="title">Confirm transfer?</span>
    <span slot="description">Review the operation before committing it to the vault.</span>
    <p>This action updates the shared record and notifies connected devices.</p>
    <div slot="footer">
      <cv-button variant="ghost" data-dialog-close="dialog-demo-confirm">Cancel</cv-button>
      <cv-button id="dialog-demo-confirm-action" variant="primary" data-dialog-close="dialog-demo-confirm">
        Confirm transfer
      </cv-button>
    </div>
  </cv-dialog>

  <cv-dialog id="dialog-demo-delete" class="dialog-demo-controlled" type="alertdialog">
    <span slot="title">Delete vault record?</span>
    <span slot="description">This permanently removes the selected record from the current vault.</span>
    <div slot="footer">
      <cv-button variant="ghost" data-dialog-close="dialog-demo-delete">Cancel</cv-button>
      <cv-button variant="danger" data-dialog-close="dialog-demo-delete">Delete record</cv-button>
    </div>
  </cv-dialog>

  <cv-dialog id="dialog-demo-info" class="dialog-demo-controlled" modal="false">
    <span slot="title">Sync window</span>
    <p>This non-modal dialog keeps the page available while showing contextual information.</p>
    <div slot="footer">
      <cv-button variant="primary" data-dialog-close="dialog-demo-info">Got it</cv-button>
    </div>
  </cv-dialog>

  <cv-dialog id="dialog-demo-compact" class="dialog-demo-controlled" no-header>
    <p>Minimal dialog with body content only.</p>
    <div slot="footer">
      <cv-button variant="primary" data-dialog-close="dialog-demo-compact">OK</cv-button>
    </div>
  </cv-dialog>

  <cv-dialog id="dialog-demo-settings" class="dialog-demo-controlled">
    <span slot="title">Settings</span>
    <span slot="header-close">X</span>
    <p>Dialog content here.</p>
  </cv-dialog>
</div>

<script>
  document.querySelectorAll('.dialog-demo-shell[data-demo="dialog"]:not([data-ready])').forEach((shell) => {
    shell.dataset.ready = 'true'
    const dialogs = new Map([...shell.querySelectorAll('cv-dialog[id]')].map((dialog) => [dialog.id, dialog]))
    const setDialogOpen = (id, open) => {
      const dialog = dialogs.get(id)
      if (dialog) dialog.open = open
    }

    shell.querySelectorAll('[data-dialog-open]').forEach((control) => {
      control.addEventListener('click', () => setDialogOpen(control.dataset.dialogOpen, true))
    })

    shell.querySelectorAll('[data-dialog-close]').forEach((control) => {
      control.addEventListener('click', () => setDialogOpen(control.dataset.dialogClose, false))
    })
  })
</script>

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

AttributeTypeDefaultDescription
openBooleanfalseWhether the dialog is visible
modalBooleantrueEnables modal behavior (focus trap, scroll lock, backdrop)
typeString"dialog"ARIA role type: dialog | alertdialog
close-on-escapeBooleantrueWhether Escape key closes the dialog
close-on-outside-pointerBooleantrueWhether clicking outside closes the dialog
close-on-outside-focusBooleantrueWhether focusing outside closes the dialog
initial-focus-idStringId of element to focus when dialog opens
no-headerBooleanfalseHides the header (title, description, header close button)

Slots

SlotDescription
(default)Dialog body content
titleDialog title text
descriptionDescription text below the title
header-closeIcon content for the header close button (defaults to X)
footerFooter content (action buttons, etc.)

CSS Parts

PartElementDescription
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

PropertyDefaultDescription
--cv-dialog-widthvar(--cv-dialog-width-m, min(560px, calc(100vw - 32px)))Preferred dialog inline size
--cv-dialog-width-sapp/theme providedStandard small dialog inline size
--cv-dialog-width-mapp/theme providedStandard medium dialog inline size
--cv-dialog-width-lapp/theme providedStandard large dialog inline size
--cv-dialog-width-xlapp/theme providedStandard extra-large dialog size
--cv-dialog-max-heightcalc(100dvh - 32px)Maximum block size before scrolling
--cv-dialog-header-spacingvar(--cv-space-4, 16px)Header padding
--cv-dialog-body-spacingvar(--cv-space-4, 16px)Body padding
--cv-dialog-footer-spacingvar(--cv-space-4, 16px)Footer padding
--cv-dialog-overlay-colorvar(--cv-color-overlay)Backdrop overlay color
--cv-dialog-border-radiusvar(--cv-radius-lg, 14px)Panel border radius
--cv-dialog-transition-durationvar(--cv-duration-fast, 120ms)Presence transition duration
--cv-dialog-transition-easing-openvar(--cv-easing-decelerate)Opening transition easing
--cv-dialog-transition-easing-closevar(--cv-easing-accelerate)Closing transition easing
--cv-dialog-content-closed-transformtranslate3d(0, 8px, 0) scale(0.98)Panel transform while closed/closing
--cv-dialog-content-open-transformtranslate3d(0, 0, 0) scale(1)Panel transform while open

Visual States

Host selectorDescription
: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

EventDetailDescription
cv-input{open: boolean}Fires when open state changes via user interaction
cv-change{open: boolean}Fires when open state commits
cv-showFires when dialog begins to open
cv-after-showFires after dialog open animation completes
cv-hideFires when dialog begins to close
cv-after-hideFires 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 PropertyDirectionHeadless Binding
openattr → actionactions.open() / actions.close()
modalattr → optionpassed as isModal in createDialog(options)
typeattr → optionpassed as type in createDialog(options)
close-on-escapeattr → optionpassed as closeOnEscape in createDialog(options)
close-on-outside-pointerattr → optionpassed as closeOnOutsidePointer in createDialog(options)
close-on-outside-focusattr → optionpassed as closeOnOutsideFocus in createDialog(options)
initial-focus-idattr → optionpassed as initialFocusId in createDialog(options)
no-headerattr → DOMcontrols header visibility (UIKit-only, no headless binding)
Headless StateDirectionDOM 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 → effectactivates focus trap within the dialog
state.shouldLockScroll()state → effectapplies overflow: hidden to document.body
state.restoreTargetId()state → effectrestores focus after close when available
state.initialFocusTargetId()state → effectfocuses the specified element on open
  • contracts.getOverlayProps() is spread onto [part="overlay"] to apply hidden, data-open, and outside pointer/focus handlers.
  • contracts.getContentProps() is spread onto [part="content"] to apply role (dialog or alertdialog), aria-modal, aria-labelledby, aria-describedby, tabindex, and keydown handler.
  • contracts.getTitleProps() is spread onto [part="title"] to apply the id for aria-labelledby.
  • contracts.getDescriptionProps() is spread onto [part="description"] to apply the id for aria-describedby.
  • contracts.getHeaderCloseButtonProps() is spread onto [part="header-close"] to apply role, tabindex, aria-label: 'Close', and click handler.
  • UIKit dispatches cv-input and cv-change events by observing isOpen changes triggered by user interaction (not by controlled open attribute changes).
  • UIKit dispatches cv-show/cv-hide immediately and cv-after-show/cv-after-hide after 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 ancestor overflow, contain, transform, or isolation.
  • 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.

ChromVoid UIKit documentation