Skip to content

kit-dialog

<kit-dialog id="confirm">
<h2>Are you sure?</h2>
<kit-button meta-command="dialog.close" meta-prop-target="confirm">Cancel</kit-button>
<kit-button class="danger">Yes, delete</kit-button>
</kit-dialog>

<kit-dialog> wraps a native <dialog>. The wrapper provides:

  • Declarative open/close via the open property.
  • A built-in dialogModule() that handles dialog.open and dialog.close commands by id.
  • A closeEvent attribute that emits a Kitsune event on close.

It does not replace the platform’s behavior. Focus management, Escape to close, backdrop, and modal inerting all come from the native <dialog>.

@atheory-ai/kitsune-ui

import '@atheory-ai/kitsune-ui'
import { KitDialogElement, dialogModule } from '@atheory-ai/kitsune-ui'
shell.modules = [dialogModule(), /* others */]
AttributeTypeDefaultNotes
openbooleanfalseWhether the dialog is currently open. Reflects.
modal"modal" | "non-modal""modal"modal calls showModal(); non-modal calls show(). Reflects.
close-eventstringIf set, emits a meta:event of this type when the dialog closes.
idstringThe id used by dialog.open / dialog.close commands to target this dialog.
MethodNotes
show()Opens the dialog (modal or not, based on modal attr).
close(returnValue?: string)Closes the dialog with an optional return value.
EventDetailFires
kit-dialog-closeWhen the dialog closes (Escape, native button, programmatic). Composed and bubbles.
meta:event{ type: <closeEvent> }When the dialog closes, only if close-event is set. Composed and bubbles.
PropertyDefault
--kit-color-surfaceCanvas
--kit-color-textCanvasText
--kit-color-borderCanvasText
--kit-radius-md0.5rem
--kit-space-41rem
--kit-focus-ringHighlight

Style the backdrop with ::backdrop on the inner <dialog>, but you may need to use the ::part(backdrop) pattern in a future version. Today, override :host dialog::backdrop from the parent.

The simplest pattern. With dialogModule() installed:

<kit-button meta-command="dialog.open" meta-prop-target="my-dialog">Open</kit-button>
<kit-dialog id="my-dialog">
<h2>Hello</h2>
<kit-button meta-command="dialog.close" meta-prop-target="my-dialog">Close</kit-button>
</kit-dialog>

No JS handler needed. dialogModule() finds the dialog by id and calls show()/close().

const dialog = document.getElementById('my-dialog') as KitDialogElement
dialog.show()
// later
dialog.close()

Or via the runtime:

await shell.runtime.command({ type: 'dialog.open', payload: { target: 'my-dialog' } })
await shell.runtime.command({ type: 'dialog.close', payload: { target: 'my-dialog' } })
<kit-dialog id="confirm" close-event="confirm.dismissed">
<!-- ... -->
</kit-dialog>

When the dialog closes (Escape, button, programmatic), it dispatches a composed meta:event with type: 'confirm.dismissed'. The boundary picks it up and emits a runtime event.

This is a one-line way to track “dialog dismissed” interactions.

Native pattern for confirms:

<kit-dialog id="confirm">
<form method="dialog">
<p>Delete this note?</p>
<menu>
<kit-button type="submit" value="cancel">Cancel</kit-button>
<kit-button type="submit" value="confirm" class="danger">Yes</kit-button>
</menu>
</form>
</kit-dialog>

The dialog’s close event has returnValue set to the clicked button’s value. The native dialog handles closing automatically.

  • showModal() traps focus, inerts the rest of the page, and announces as a dialog.
  • Escape closes the dialog (browser default).
  • Use a real <h2> heading inside; it acts as the dialog’s accessible name when it has aria-labelledby (which the underlying <dialog> infers in modern browsers).
  • Don’t add custom focus traps — the native dialog already does it.