J3W1 / DESIGN SYSTEM
Dialog
A native dialog on the overlay surface behind a 1px overlay border and the backdrop; modal when shown, with trapped focus and Escape to close.
stable · LIGHT DOM · 1.1.0
Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
<j3w1-dialog id="example-dialog-default">
<dialog class="dialog" role="alertdialog" aria-labelledby="example-dialog-default-dg-confirm-title" aria-describedby="example-dialog-default-dg-confirm-text">
<div class="dialog-header">
<h2 class="dialog-title" id="example-dialog-default-dg-confirm-title">Save changes?</h2>
<button class="dialog-close" type="button" aria-label="Close">✕</button>
</div>
<div class="dialog-body">
<p class="dialog-text" id="example-dialog-default-dg-confirm-text">Three settings changed since the last save. Unsaved changes are kept locally until you publish.</p>
</div>
<div class="dialog-actions">
<button class="dialog-button" type="button" data-close="cancel">Keep editing</button>
<button class="dialog-button dialog-button-primary" type="button" data-action="confirm">Save</button>
</div>
<p class="dialog-trap-note">Focus is trapped: Tab cycles inside the dialog; Escape closes it.</p>
</dialog>
<button class="button" type="button" data-open="">Open dialog</button></j3w1-dialog>alert-dialog
<j3w1-dialog id="example-dialog-alert-dialog">
<dialog class="dialog" role="alertdialog" aria-labelledby="example-dialog-alert-dialog-dg-delete-title" aria-describedby="example-dialog-alert-dialog-dg-delete-text">
<div class="dialog-header">
<h2 class="dialog-title" id="example-dialog-alert-dialog-dg-delete-title">Delete profile "workstation"?</h2>
<button class="dialog-close" type="button" aria-label="Close">✕</button>
</div>
<div class="dialog-body">
<p class="dialog-text" id="example-dialog-alert-dialog-dg-delete-text">The profile and its two overlays are removed. This cannot be undone.</p>
</div>
<div class="dialog-actions">
<button class="dialog-button" type="button" data-close="cancel">Cancel</button>
<button class="dialog-button dialog-button-destructive" type="button" data-action="delete">Delete profile</button>
</div>
<p class="dialog-trap-note">Focus is trapped: Tab cycles inside the dialog; Escape cancels.</p>
</dialog>
<button class="button" type="button" data-open="">Open dialog</button></j3w1-dialog>form
<j3w1-dialog id="example-dialog-form">
<dialog class="dialog" aria-labelledby="example-dialog-form-dg-form-title" aria-describedby="example-dialog-form-dg-form-text">
<div class="dialog-header">
<h2 class="dialog-title" id="example-dialog-form-dg-form-title">New profile</h2>
<button class="dialog-close" type="button" aria-label="Close">✕</button>
</div>
<form class="dialog-body" method="dialog">
<p class="dialog-text" id="example-dialog-form-dg-form-text">The profile starts from the default tokens.</p>
<div class="dialog-field">
<label class="dialog-field-label" for="example-dialog-form-dg-form-name">Profile name</label>
<input class="dialog-field-input" id="example-dialog-form-dg-form-name" type="text" value="laptop" aria-describedby="example-dialog-form-dg-form-help" name="example-dialog-form-dg-form-name">
<p class="dialog-field-help" id="example-dialog-form-dg-form-help">Lowercase letters, digits and hyphens.</p>
</div>
<div class="dialog-actions">
<button class="dialog-button" type="button" data-close="cancel">Cancel</button>
<button class="dialog-button dialog-button-primary" type="submit">Create</button>
</div>
</form>
<p class="dialog-trap-note">Focus is trapped: Tab cycles inside the dialog; Enter submits; Escape cancels.</p>
</dialog>
<button class="button" type="button" data-open="">Open dialog</button></j3w1-dialog>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@1.1.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/dialog.css';
import '@j3w1/ui/register/dialog';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@1.1.0 j3w1-ui copy dialog --out ./vendor/j3w1/dialogThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
open | boolean | Native modal state. |
returnValue | string | Native dialog result. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
show | (): void | Open using native showModal and remember the opener. |
close | (value?: string): void | Close with a result value. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-open | CustomEvent | The modal opened. |
j3w1-close | CustomEvent | The modal closed and restored available opener focus. |
j3w1-cancel | CustomEvent | Native cancel request; prevent default to keep the modal open. |
j3w1-action | CustomEvent | An explicit dialog action; prevent default to keep it open. |
Dependencies and verification
Registered components: none. Style dependencies: button.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Purpose
A question or a small task that must be answered before the page continues:
confirm a destructive action, name a new profile, sign in again. It takes
the whole screen’s attention, so it is short, has a clear title, and always
offers a way out. The demonstration renders the dialog open in flow, with a
wrapper standing in for the backdrop; a host always uses showModal().
Anatomy
The backdrop, color.surface.backdrop over the page; the dialog on
color.surface.overlay behind a 1px color.border.overlay; the header with
the title in color.text.bright and Close; the body in color.text.default;
the actions row. Confirmation semantics do not prescribe action tone (D-015):
ordinary confirmations use primary. A destructive confirmation uses the filled destructive button,
color.action.destructive.filled-bg with color.action.destructive.filled-text;
the form dialog holds a labelled text field on color.surface.input.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | the dialog open on its backdrop | open; the page behind is inert |
| open | as default; opacity 0 → 1 within 150 ms on capable hosts | open; focus inside the dialog |
| closed | nothing: the dialog and its backdrop are absent | no open; focus back on the opener |
| focus-trapped | the dialog itself carries the container ring, 2px solid color.interaction.focus.ring-container at −3px, when it holds focus; the note says Tab cycles | Tab never leaves; the note text |
| hover | Close background → color.action.tertiary.hover-bg; other buttons take their tone’s hover | cursor: pointer |
| focus-visible | ring 1px dashed color.interaction.focus.ring at −2px on the focused control; primary uses color.interaction.focus.ring-container, destructive uses color.action.destructive.filled-text (D-015) | the ring |
| reduced-motion | no opacity transition; the dialog is simply present or absent | prefers-reduced-motion |
A dialog is never disabled; a busy dialog disables its confirm button and shows the ⋯ glyph on it.
Keyboard
Tab and Shift+Tab cycle inside the dialog; nothing behind it is reachable. Escape closes and cancels, and focus returns to the opener. Enter submits a form dialog; elsewhere it only activates the focused button, so a destructive confirm is never triggered by Enter on the dialog itself. On open, focus goes to the first focusable element, or to the dialog when the first element is destructive; on close, back to the opener.
Accessibility
The dialog is named by its title through aria-labelledby and described by
its first paragraph through aria-describedby. Confirmations use
role="alertdialog". showModal() makes the rest of the page inert and
supplies the top layer; the theme never re-implements the trap on a
non-modal dialog. The backdrop is decorative and not a click target for
cancel unless Close is also present. Contrast: text 7.92:1 and title 9.49:1
on the overlay surface, the overlay border 4.30:1, secondary buttons 6.84:1
on hover, destructive confirm text 4.58:1 on its fill, the container ring
9.49:1.
Portability
A box, a border, a dimmed backdrop and the button tones. Toolkits with a native modal window use it with the overlay border on the frame and record that the backdrop is the host’s. Toolkits without a top layer draw the backdrop as a full-window overlay under the dialog.
Non-examples
A glowing or shadowed card. Rounded corners. A dialog that slides or scales
in. <dialog> shown with show() and a hand-written focus trap. A dialog
with no title, or with the title only in the accessible name. A destructive
confirm as the default Enter action. A backdrop that closes the dialog with
no Close button. A dialog stacked on a dialog.