Dialog
A dialog is a modal window over the page: people finish or dismiss it before they go back. tk-dialog holds a short task; tk-confirm-dialog confirms one action.
<tk-button onclick="document.getElementById('dialog').show()">Rename project</tk-button>
<tk-dialog id="dialog" heading="Rename project">
<tk-field label="Project name"><tk-input value="Aurora"></tk-input></tk-field>
<tk-button slot="footer" onclick="this.closest('tk-dialog').close()">Cancel</tk-button>
<tk-button slot="footer" variant="primary" onclick="this.closest('tk-dialog').close()">Rename</tk-button>
</tk-dialog>
<tk-button tone="danger" onclick="document.getElementById('confirm').show()">Delete project</tk-button>
<tk-confirm-dialog id="confirm" heading="Delete project?" message="This permanently deletes 'Aurora' and all its data." confirm-label="Delete project" danger></tk-confirm-dialog>Anatomy
| Part | tk-dialog |
tk-confirm-dialog |
|---|---|---|
| heading | heading: the title and accessible name, required |
heading: a specific question, required |
| close button | built in, top right | none; Cancel is the way out |
| body | the default slot; it scrolls | message: one line about the consequence |
| footer | the footer slot: your buttons, right-aligned |
Cancel and the confirm button, built in |
| backdrop | dims the page and blocks it | the same |
dialog part |
the panel | the panel |
Variants
<tk-button tone="danger" onclick="document.getElementById('delete').show()">Delete project</tk-button>
<tk-confirm-dialog id="delete" heading="Delete project?" message="This permanently deletes Aurora and all its data." confirm-label="Delete" danger></tk-confirm-dialog>tk-dialog: a task with content. You fill the body and the footer.tk-confirm-dialog: confirms one action. Set it with attributes only: no slots, no close button, no scrolling, always 320px wide.confirm-labelis required; without it, only Cancel renders.danger: for an action that deletes something or can't be undone. The confirm button becomes adanger button , red-tinted with a red label, and focus starts on Cancel. Withoutdanger, the confirm button isprimaryand takes focus.
For a large loss, add friction in a tk-dialog: keep the danger button disabled until the
user checks a
Sizes
size |
Width | Use for |
|---|---|---|
small |
320px | short decisions with some content |
medium (default) |
480px | most forms and tasks |
large |
640px | wide content, such as a two-column form or a table |
- The dialog stays inside the viewport with
space-16(16px) to spare on each side, down to a 320 × 256px viewport. - Long content scrolls in the body while the heading and footer stay put. The content fades at an edge while there's more beyond it. Nothing scrolls sideways; use a wider size.
tk-confirm-dialoghas nosize: it is 320px wide and doesn't scroll, so keep it short.
Opening and closing
<tk-button onclick="document.getElementById('rename').show()">Rename project</tk-button>
<tk-dialog id="rename" heading="Rename project">
<form id="rename-form">
<tk-field label="Project name"><tk-input name="name" value="Aurora" autofocus></tk-input></tk-field>
</form>
<tk-button slot="footer" onclick="this.closest('tk-dialog').close()">Cancel</tk-button>
<tk-button slot="footer" variant="primary" type="submit" form="rename-form">Rename</tk-button>
</tk-dialog>const dialog = document.getElementById('rename');
dialog.querySelector('form').addEventListener('submit', (event) => {
event.preventDefault();
// save the name, then:
dialog.close('renamed');
});show()opens the dialog, always as a modal.close(value)closes it, andreturnValueholds the value in yourcloselistener. Focus goes back to the element that opened it.- The footer sits outside your form, so give the submit button
formwith the form's id.<form method="dialog">doesn't close the dialog; callclose()on submit. - Put
autofocuson the first field.
| Close path | tk-dialog |
tk-confirm-dialog |
|---|---|---|
Esc |
closes | closes, as Cancel |
| close button | closes | — |
| backdrop click | closes, unless the dialog holds a <form> |
does nothing |
| Cancel | closes; you wire it | closes |
| main action | you wire it | closes with returnValue "confirm" |
- Closing never saves anything. Every way out except the main action leaves
returnValueas""intk-dialogand"cancel"intk-confirm-dialog. persistentturns offEscand the backdrop. The close button and the dialog's buttons still work. Keep it for rare cases, such as accepting legal terms.- Open at most one confirm over one dialog.
Esccloses the top one.
Unsaved changes
Esc and a backdrop click fire a cancel event first, and preventDefault() keeps the
dialog open. Use it to ask before typed work is lost. The close button and Cancel don't fire
cancel.
<tk-confirm-dialog id="discard" heading="Discard changes?" message="Your edits will be lost." cancel-label="Continue editing" confirm-label="Discard changes" danger></tk-confirm-dialog>
<script>
const dialog = document.getElementById('rename');
const discard = document.getElementById('discard');
dialog.addEventListener('cancel', (event) => {
if (!hasUnsavedChanges()) return;
event.preventDefault();
discard.show();
});
discard.addEventListener('close', () => {
if (discard.returnValue === 'confirm') dialog.close();
});
</script>Waiting for the server
preventDefault() on the confirm button's click keeps tk-confirm-dialog open while your
request runs.
const deleteDialog = document.getElementById('delete');
deleteDialog.addEventListener('click', async (event) => {
const button = event.composedPath().find((node) => node.dataset?.action === 'confirm');
if (!button) return;
event.preventDefault();
button.loading = true;
await deleteProject();
button.loading = false;
deleteDialog.close('confirm');
});When to use
- Use
tk-dialogfor a short task that must be done before anything else, such as a small form or a decision that needs context. - Use
tk-confirm-dialogbefore an uncommon action that can't be undone: delete, discard, disconnect. - Don't confirm a common action that can be undone. Do it, and offer undo in a
Toast . - Use a page for a long flow, a large form, a repeated task, or content people keep open.
- Never open a dialog when a page loads, or to show information nobody can act on.
- Use a
Sheet for a panel that slides in from an edge. To pick a value, useSelect orCombobox ; they don't block the page.
Content
- Heading: a verb and an object, as a specific question or statement: "Delete project?", "Rename project". Never "Are you sure?" or "Error".
- Repeat the trigger: a "Delete project…" menu item opens "Delete project?" with a "Delete" button.
- Message: the consequence, only if it adds something. Full sentences, the most important first. Don't explain the buttons.
- The confirm button names the action: "Delete", "Remove access". Never OK, Yes, or Done.
- Cancel stays "Cancel". The one exception is the unsaved-changes pair: "Continue editing" and "Discard changes".
- Two footer buttons is usual, three at most. One
primary, on the right, with Cancel just before it. - Use sentence case.
More in
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Confirm an uncommon action that can't be undone | Confirm a common action; offer undo instead |
| Write "Delete" on the confirm button | Write "OK" or "Yes" |
| Keep a confirm to a heading, one line, and two buttons | Put fields or long text in a confirm |
Ask "Discard changes?" through cancel |
Turn on persistent to protect a form |
| Open one confirm over a dialog at most | Stack dialogs to move through steps |
| Open a dialog when someone asks for it | Open one when the page loads |
Related
Button : the footer actions and the danger tone.Icon button : the built-in close button.Menu : its danger items open a confirm.Sheet : the same modal panel, sliding in from an edge.Toast : undo for actions you don't confirm.Checkbox andInput : the extra-friction confirm.
API
Attributes
tk-dialog:
| Attribute | Type | Default | What it does |
|---|---|---|---|
heading |
string | none | the title and accessible name; required |
size |
small | medium | large |
medium |
width 320 / 480 / 640px |
persistent |
boolean | false |
Esc and backdrop clicks don't close it |
backdrop-blur |
boolean | false |
blurs the page behind the backdrop |
tk-confirm-dialog:
| Attribute | Type | Default | What it does |
|---|---|---|---|
heading |
string | none | the question; required |
message |
string | none | the consequence line; read as the description |
confirm-label |
string | none | the confirm button's label; required |
cancel-label |
string | Cancel |
the Cancel button's label |
danger |
boolean | false |
danger confirm button; focus starts on Cancel |
persistent |
boolean | false |
Esc doesn't close it |
backdrop-blur |
boolean | false |
blurs the page behind the backdrop |
backdrop-blur is off by default because blur costs performance. Where the browser can't
blur, the backdrop just dims.
Slots
| Slot | Content |
|---|---|
| default | tk-dialog's body |
footer |
tk-dialog's buttons, usually tk-buttons |
tk-confirm-dialog has no slots.
Methods and properties
| Name | What it does |
|---|---|
show() |
opens the dialog as a modal |
close(returnValue?) |
closes it; without a value, it closes as a dismissal |
returnValue |
the value of the last close: "confirm" or "cancel" on the confirm dialog |
open |
read-only, true while open; the open attribute mirrors it but doesn't open anything |
Events
| Event | When |
|---|---|
close |
after the dialog closes, by any path; read returnValue |
cancel |
Esc or a backdrop click asks to close; preventDefault() keeps it open |
Both fire on the element and don't bubble, so listen on the dialog itself. On
tk-confirm-dialog, cancel comes from Esc only.
CSS
::part(dialog): the panel.
Tokens
| Part | Tokens |
|---|---|
| backdrop | surface/backdrop; with backdrop-blur, also blur-backdrop (8px) |
| panel | surface/raised, shadow-3, radius-20; in high contrast, a 1px border/default |
| heading | text/default, Heading Medium (20/24) |
| body, message | text/default, Body Medium (16/24) |
| spacing | space-16 around the regions, space-8 between buttons |
| buttons |
The backdrop is darker in dark and high contrast. Values per theme:
Accessibility
Built in:
- A native modal
<dialog>: the page behind can't be clicked, reached with Tab, or read by screen readers. role="dialog", orrole="alertdialog"for the confirm, named by the heading. The confirm'smessageis its description.- Focus moves in when it opens, stays inside, and returns to the opener when it closes.
- First focus: in
tk-dialog, the element withautofocus, otherwise the heading. Intk-confirm-dialog, Cancel withdanger, otherwise the confirm button. - The page doesn't scroll while a dialog is open.
- It opens with a short fade and scale; with reduced motion, a fade only.
Your part:
- Put
autofocuson the first field of a form. Never start on a link. - If the opener is gone after closing, move focus yourself in the
closelistener. - Put live regions inside the dialog, not on the page behind it.
- Name the object in the heading and the action on the confirm button.
| Key | Action |
|---|---|
Esc |
closes the top layer: an open menu, select, or combobox inside the dialog first, then the dialog |
Tab / Shift+Tab |
moves through the dialog only |
Known limits
- Screen readers list the dialog's header and footer as page landmarks (banner and contentinfo) while it is open.