‮Skip to content‬
uiify

Dialog

‮Trigger, content and close parts around a native, initially closed `<dialog>`.‬

0 kB client JS≤ 0.5 kB gzipped

Anatomy

<Dialog.Trigger target="settings">Open</Dialog.Trigger>
<Dialog.Content aria-labelledby="settings-title" id="settings">
  <h2 id="settings-title">Settings</h2>
  <Dialog.Close target="settings">Close</Dialog.Close>
</Dialog.Content>

Live demo

This demo also runs with JavaScript disabled.

Dialog

Minimal

Settings

Composition — confirm dialog

Delete this project?

This cannot be undone.

Dialog.Trigger

Renders Invoker. Emits command="show-modal", commandfor={target}, data-uiify-dialog="", data-part="trigger".

NameTypeDefaultNotes
targetstringRequired. `id` of the `Dialog.Content` this button opens; the browser resolves it at click time.
asNativeButtonAs<TAs>"button"See Invoker.

Dialog.Content

Renders <dialog>. Emits data-uiify-dialog="", data-part="content"; `open` and `popover` are omitted from the prop type — a native part is always initially closed.

NameTypeDefaultNotes
idstringRequired. Unique id supplied by the consumer; referenced by trigger and close `target`.
aria-labelledbystringRequired unless `aria-label` is given — exactly one of the two, enforced by the type.
aria-labelstringRequired unless `aria-labelledby` is given.

Dialog.Close

Renders Invoker. Emits command="request-close", commandfor={target}, data-uiify-dialog="", data-part="close".

NameTypeDefaultNotes
targetstringRequired. `id` of the `Dialog.Content` this button closes.
asNativeButtonAs<TAs>"button"See Invoker.

Rendered DOM

<button data-uiify-dialog="" data-part="trigger" command="show-modal" commandfor="settings" type="button">Open</button><dialog aria-labelledby="settings-title" id="settings" data-part="content" data-uiify-dialog=""><h2 id="settings-title">Settings</h2><button data-uiify-dialog="" data-part="close" command="request-close" commandfor="settings" type="button">Close</button></dialog>

CSS contract

dialog:modal {
  /* true while open and blocking interaction */
}

dialog[open] {
  /* true while open, native or via defaultOpen */
}

Build your own

import { Dialog } from "@gunkin/uiify/elements/dialog";

export function ConfirmDialog() {
  return (
    <>
      <Dialog.Trigger target="delete-confirm">Delete project</Dialog.Trigger>
      <Dialog.Content aria-labelledby="delete-confirm-title" id="delete-confirm">
        <h2 id="delete-confirm-title">Delete this project?</h2>
        <p>This cannot be undone.</p>
        <Dialog.Close target="delete-confirm">Cancel</Dialog.Close>
        <Dialog.Close target="delete-confirm">Delete</Dialog.Close>
      </Dialog.Content>
    </>
  );
}

Used by

Gotchas

  • `id` must be stable across server and client renders — `Dialog.Trigger`/`Dialog.Close`'s `target` has to match it exactly.
  • Omitting both `aria-labelledby` and `aria-label` on `Dialog.Content` is a type error, not a runtime check — an unnamed `<dialog>` has no ARIA fallback.
  • WebKit does not restore focus to the trigger after the dialog closes — a platform gap, not a bug in this element.
  • `Dialog.Close` sends `request-close`, so the cancelable `cancel` event fires before the dialog closes.

Import

import { Dialog } from "@gunkin/uiify/elements/dialog";