7. Confirm with kit-dialog
What we want
Section titled “What we want”Each note has a delete button. Clicking it opens a confirmation dialog. The user confirms, and the note disappears. Built on the platform’s <dialog> and the built-in dialogModule.
Add a delete button to the editor
Section titled “Add a delete button to the editor”Update renderEditor:
function renderEditor(note: Note): string { return ` <kit-boundary surface="note-editor" feature="notes" entity-type="note" entity-id="${note.id}"> <article> <h2>${escape(note.title)}</h2> <pre class="body">${escape(note.body)}</pre> <small>Last updated ${new Date(note.updatedAt).toLocaleString()}</small>
<menu class="actions"> <kit-button meta-event="note.delete_requested" meta-prop-id="${escape(note.id)}" meta-prop-title="${escape(note.title)}" meta-command="dialog.open" meta-prop-target="confirm-delete" > Delete </kit-button> </menu> </article> </kit-boundary> `}The Delete button declares both an event and a command:
note.delete_requestedis a fact for observers (analytics, audit).dialog.openopens the confirm dialog by id.
Both fire on the same click. Event first, command after. The runtime handles both — the button doesn’t know either is being observed.
Add the confirm dialog
Section titled “Add the confirm dialog”In index.html, near the bottom of <kit-shell>:
<kit-dialog id="confirm-delete"> <h2>Delete this note?</h2> <p id="confirm-delete-detail">This cannot be undone.</p> <menu> <kit-button meta-command="dialog.close" meta-prop-target="confirm-delete" > Cancel </kit-button> <kit-button id="confirm-delete-button" class="danger"> Yes, delete </kit-button> </menu></kit-dialog>Capture the pending id
Section titled “Capture the pending id”Listen for note.delete_requested and stash the pending id:
let pendingDelete: { id: string; title: string } | null = null
shell.runtime.on('note.delete_requested', (event) => { pendingDelete = { id: String(event.payload?.id ?? ''), title: String(event.payload?.title ?? ''), } document.getElementById('confirm-delete-detail')!.textContent = `"${pendingDelete.title}" will be removed.`})
document.getElementById('confirm-delete-button')!.addEventListener('click', async () => { if (!pendingDelete) return
await shell.runtime.command({ type: 'note.delete', payload: { id: pendingDelete.id }, })
await shell.runtime.command({ type: 'dialog.close', payload: { target: 'confirm-delete' }, })
pendingDelete = null})The “Yes, delete” button isn’t a meta-command="note.delete" because the payload is dynamic (we need the pending id). For static commands, the metadata protocol is enough. For dynamic ones, dispatch programmatically.
CSS for the danger button
Section titled “CSS for the danger button”kit-button.danger { --kit-button-bg: var(--kit-color-error, oklch(58% 0.18 25)); --kit-button-color: white; --kit-button-border: var(--kit-color-error, oklch(58% 0.18 25));}<kit-button> exposes its background, foreground, and border as custom properties. Override at the host level — no internal selector knowledge required.
What we have now
Section titled “What we have now”Click Delete on a note: the dialog opens. Click Cancel: it closes. Click Yes, delete: the note vanishes from both the list and the audit panel updates. Native focus management traps inside the dialog; Escape closes; the backdrop renders without writing CSS.
What you didn’t write:
- A focus trap
- An Escape handler
- A backdrop element
- An inert mechanism for the rest of the page
- A modal-open detection class
The browser provided all of it.
A pattern: declarative open + programmatic confirm
Section titled “A pattern: declarative open + programmatic confirm”A common confirmation flow in Kitsune apps:
- Open is declarative:
meta-command="dialog.open" meta-prop-target="...". - Cancel is declarative:
meta-command="dialog.close" meta-prop-target="...". - Confirm is programmatic: a click handler that dispatches the actual command and then closes the dialog.
If the confirm payload is also static (e.g., always the same), even confirm can be declarative. Most real flows have dynamic payloads (which note? which user?).
What’s next
Section titled “What’s next”Toasts. We’ve already installed notificationModule() — now we’ll fire it on every save, update, and delete. Two lines of code per place. Zero new modules.