Dialog
Temporarily interrupt a task with modal content and managed focus.
Try it
<hui-dialog id="playground-dialog" strategy="native">
<hui-dialog-trigger>Open settings</hui-dialog-trigger>
<hui-dialog-content>
<hui-dialog-title>Settings</hui-dialog-title>
<hui-dialog-description>Update your preferences.</hui-dialog-description>
<label>Display name <input value="Ada" /></label>
<hui-dialog-close>Cancel</hui-dialog-close>
</hui-dialog-content>
</hui-dialog>- data-state
- Waiting for enhancement
No component events yet.
Usage
Use hui-dialog for modal content that temporarily interrupts the current task and requires focus containment. Use a popover for supplemental non-modal content. Do not use a dialog for routine page content or a tooltip-sized description.
Anatomy
- Root:
hui-dialog - Trigger:
hui-dialog-trigger - Content surface:
hui-dialog-content - Heading and description:
hui-dialog-title,hui-dialog-description - Close control:
hui-dialog-close - Manual strategy overlay:
[data-part="overlay"]
Examples
<section aria-labelledby="dialog-heading">
<h2 id="dialog-heading">Account settings</h2>
<hui-dialog id="account-dialog">
<hui-dialog-trigger><b>Account settings</b></hui-dialog-trigger>
<hui-dialog-content>
<hui-dialog-title>Account settings</hui-dialog-title>
<hui-dialog-description>Update your account preferences.</hui-dialog-description>
<button data-autofocus type="button">Save changes</button>
<hui-dialog-close>Cancel</hui-dialog-close>
</hui-dialog-content>
</hui-dialog>
<div class="dialog-clip">
<hui-dialog id="manual-dialog" strategy="manual">
<hui-dialog-trigger>Manual dialog</hui-dialog-trigger>
<hui-dialog-content aria-label="Manual dialog">
<p>Manual modal content.</p>
<hui-dialog-close>Close manual dialog</hui-dialog-close>
</hui-dialog-content>
</hui-dialog>
</div>
</section>API reference
| Attribute | Binding | Exposed state | Event |
|---|---|---|---|
Root open boolean | Root data-state; content hidden; native/manual dialog open state | `data-state="open | closed"; triggeraria-expanded` |
Root default-open | Seeds open once during enhancement | Same as open | None |
Root strategy | Selects native or manual substrate | Content data-strategy | None |
Root dismissible | Controls Escape, backdrop, and overlay dismissal | None | None |
Root aria-label, aria-labelledby, aria-describedby | Trigger/content accessible names and descriptions | Guarded ARIA references | None |
Content role | Defaults to dialog; may be explicitly set to none | aria-modal="true" for dialog semantics | None |
| Content/title/description IDs | Existing IDs are preserved; generated IDs use the root ID | aria-labelledby / aria-describedby | None |
All state writes use root attributes. Closed content has an inline hidden attribute. Content IDs and references are omitted when they cannot be resolved, preventing dangling ARIA relationships.
When motion is allowed, closing content first exposes data-state="closed", aria-hidden, and inert state, then applies hidden after the surface exit completes. Manual overlay teardown and native dialog close are coordinated with that presence lifecycle. Reduced-motion and no-transition environments apply the closed state immediately.
States and styling
| State | Contract and styling guidance |
|---|---|
| Default | Closed content is hidden and the trigger exposes aria-expanded="false". |
| Hover | Use --hui-color-interactive-hover for trigger feedback. |
| Active | Use --hui-color-interactive-active for pressed trigger feedback. |
| Focus | Keep a visible focus ring with --hui-color-focus-ring and restore focus to the trigger on close. |
| Disabled | Disable the trigger and close controls with --hui-color-disabled-background and --hui-color-disabled. |
| Error | Dialog has no built-in validation state; use aria-invalid and --hui-color-danger on invalid controls inside it. |
Attributes are the source of truth. Style derived state with data-state, ARIA attributes, and the --hui-* token aliases.
Accessibility and keyboard support
| Key/action | Result |
|---|---|
| Trigger Enter/Space/click | Toggle dialog and focus the autofocus target, first tabbable, or content |
| Tab / Shift+Tab while manual dialog is open | Cycle within the dialog focus scope |
| Escape | Close and restore focus to the trigger unless dismissible="false" |
| Close button | Close and restore focus to the trigger |
| Outside overlay click | Close unless dismissible="false" |
| Native dialog cancel/backdrop interaction | Close unless dismissible="false" |
The dialog is modal: scroll is locked and siblings outside the dialog path are inert while open. Native mode delegates modal behavior to the platform and manual mode uses the shared focus trap.
SSR and forms
- SSR emits closed content with
hidden="hidden",role="dialog",aria-modal="true", deterministic IDs, and nohydro-reactive-*ortwo-waymarkers. default-openresolves to open SSR state. Client native mode promotes the content into the top layer during hydration; manual mode portals it after enhancement.- SSR never calls
showModal(), portals, locks scroll, or marks siblings inert. - A root with an explicit
idreceives stable-content,-title, and-descriptionrelationships. Id-less roots omit generated IDs and dependent ARIA references. - Without JavaScript, the trigger remains a native button and the content remains hidden. Consumers can use
default-openfor a progressive initially-open fallback; modal focus and dismissal require JavaScript.
Code
<hui-dialog id="settings">
<hui-dialog-trigger>Settings</hui-dialog-trigger>
<hui-dialog-content>
<hui-dialog-title>Settings</hui-dialog-title>
<hui-dialog-description>Update your preferences.</hui-dialog-description>
<button type="button">Save</button>
<hui-dialog-close>Cancel</hui-dialog-close>
</hui-dialog-content>
</hui-dialog>
<hui-dialog id="playground-dialog" strategy="native">
<hui-dialog-trigger>Open settings</hui-dialog-trigger>
<hui-dialog-content>
<hui-dialog-title>Settings</hui-dialog-title>
<hui-dialog-description>Update your preferences.</hui-dialog-description>
<label>Display name <input value="Ada" /></label>
<hui-dialog-close>Cancel</hui-dialog-close>
</hui-dialog-content>
</hui-dialog>