Skip to content
ShineUI
Install

ConfirmDialog

Sure about that?

<sui-confirm-dialog>OverlaysStable

About

Confirmation modal for high-stakes actions such as void, delete or close period. It composes sui-dialog with a question or warning badge, a message, an optional detail line and two buttons; confirm-variant="danger" switches to the destructive treatment. Reach for it when the decision must be made before anything else; use sui-confirm-popup for a low-stakes row action.

Usage

<sui-confirm-dialog confirm-variant="danger" heading="Void invoice INV-1042?" confirm-label="Void invoice" message="This posts a reversing entry dated today." detail="Voided documents remain in the audit trail for 7 years."></sui-confirm-dialog> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-confirm-dialog"); el.addEventListener("suiClose", (e) => console.log(e.detail)); </script>
import { SuiConfirmDialog } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiConfirmDialog confirmVariant="danger" heading="Void invoice INV-1042?" confirmLabel="Void invoice" message="This posts a reversing entry dated today." detail="Voided documents remain in the audit trail for 7 years." onSuiClose={(e) => console.log(e.detail)} />; }
<script setup> import { SuiConfirmDialog } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiConfirmDialog confirm-variant="danger" heading="Void invoice INV-1042?" confirm-label="Void invoice" message="This posts a reversing entry dated today." detail="Voided documents remain in the audit trail for 7 years." @sui-close="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiConfirmDialog } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiConfirmDialog], template: `<sui-confirm-dialog confirm-variant="danger" heading="Void invoice INV-1042?" confirm-label="Void invoice" message="This posts a reversing entry dated today." detail="Voided documents remain in the audit trail for 7 years." [(open)]="open" (suiClose)="onChange($event)"></sui-confirm-dialog>`, }) export class Example { open = false; onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The shell is sui-dialog, so focus is trapped and the heading and message are announced on open; there is no corner X, and focus opens on Cancel so Enter cannot confirm by accident. Cancel, Escape and the scrim emit suiClose with the reason, and the confirm button emits suiConfirm.

<sui-button variant="danger" label="Void invoice"></sui-button> <sui-confirm-dialog confirm-variant="danger" heading="Void invoice INV-1042?" confirm-label="Void invoice" message="This posts a reversing entry dated today." detail="Voided documents remain in the audit trail for 7 years."></sui-confirm-dialog>
// setup(root): properties and events setup(root) { const dialog = root.querySelector('sui-confirm-dialog'); root.querySelector('sui-button').addEventListener('click', () => { dialog.open = true; }); }

Question and danger

The default treatment asks a question with the brand button; confirm-variant="danger" switches to the warning badge and the danger button. The detail slot carries a richer second line.

Reopening a period is an admin action and is recorded in the audit trail.
<div style="display:flex;gap:10px;flex-wrap:wrap"> <sui-button id="ask-post" variant="outline" label="Post journal entry"></sui-button> <sui-button id="ask-close" variant="outline" label="Close period"></sui-button> </div> <sui-confirm-dialog id="post-dialog" heading="Post this journal entry?" confirm-label="Post entry" message="JE-2044 debits Operating 4402 and credits Payroll 4410."></sui-confirm-dialog> <sui-confirm-dialog id="close-dialog" confirm-variant="danger" heading="Close July?" confirm-label="Close July" message="Entries dated in July can no longer be edited."> <span slot="detail">Reopening a period is an admin action and is recorded in the audit trail.</span> </sui-confirm-dialog>
// setup(root): properties and events setup(root) { [['ask-post', 'post-dialog'], ['ask-close', 'close-dialog']].forEach(([b, d]) => { const dialog = root.querySelector('#' + d); root.querySelector('#' + b).addEventListener('click', () => { dialog.open = true; }); }); }

Typed confirmation

confirm-disabled keeps the confirm button disabled until the reader types the workspace name, the pattern for a deletion that cannot be undone. The button takes its disabled look and Enter cannot press it; Cancel and Escape still close. The host clears the flag when the text matches.

<sui-button id="delete-ws" variant="outline" tone="danger" label="Delete workspace"></sui-button> <sui-confirm-dialog id="delete-dialog" confirm-variant="danger" confirm-disabled heading="Delete Riverbend Dental?" confirm-label="Delete workspace" message="Every record in the workspace is removed. Type the workspace name to confirm."> <div style="margin-top:12px"><sui-input-text id="delete-name" label="Workspace name" size="md" autocomplete="off" placeholder="Riverbend Dental"></sui-input-text></div> </sui-confirm-dialog>
// setup(root): properties and events setup(root) { const dialog = root.querySelector('#delete-dialog'); const name = root.querySelector('#delete-name'); root.querySelector('#delete-ws').addEventListener('click', () => { dialog.open = true; }); const reset = () => { name.value = ''; dialog.confirmDisabled = true; }; name.addEventListener('suiInput', (e) => { dialog.confirmDisabled = e.detail.trim() !== 'Riverbend Dental'; }); dialog.addEventListener('suiClose', reset); dialog.addEventListener('suiConfirm', reset); }

Async confirm

suiConfirm is vetoed with preventDefault while the request runs, so the confirm button stays busy and Cancel is locked. The host clears open when the request resolves.

Nothing voided yet.
<sui-button id="void-async" variant="danger" label="Void invoice"></sui-button> <div id="void-log" style="margin-top:10px;font-size:12px;color:var(--ink-muted)">Nothing voided yet.</div> <sui-confirm-dialog id="void-dialog" confirm-variant="danger" heading="Void invoice INV-1042?" confirm-label="Void invoice" message="This posts a reversing entry dated today."></sui-confirm-dialog>
// setup(root): properties and events setup(root) { const dialog = root.querySelector('#void-dialog'); const log = root.querySelector('#void-log'); root.querySelector('#void-async').addEventListener('click', () => { dialog.open = true; }); dialog.addEventListener('suiConfirm', (e) => { e.preventDefault(); dialog.pending = true; log.textContent = 'Posting the reversing entry.'; setTimeout(() => { dialog.pending = false; dialog.open = false; log.textContent = 'Voided INV-1042.'; }, 1400); }); dialog.addEventListener('suiClose', (e) => { if (dialog.pending) { e.preventDefault(); return; } log.textContent = 'Kept INV-1042 (' + e.detail + ').'; }); }

Keyboard

KeyAction
Tab Shift+TabCycles between Cancel and the confirm button and never leaves the dialog. Focus opens on Cancel, the least destructive action, as it does on the confirm popup.
EscapeAsks to close with reason escape. Only the topmost open modal answers it.
Enter SpacePresses the focused button: Cancel asks to close with reason cancel, the confirm button emits suiConfirm. While confirm-disabled is set the confirm button is disabled, so Enter cannot press it.

API

<sui-confirm-dialog>

Properties

PropertyAttributeTypeDefaultDescription
cancelLabelcancel-labelstringstring | undefinedLabel for the dismissing action.
confirmDisabledconfirm-disabledbooleanfalseDisables the confirming button, for a typed confirmation: keep it set until the text the reader types matches, then clear it. The button takes its own disabled look and reports disabled, and neither a click nor Enter confirms while it is set; Cancel and Escape still close.
confirmLabelconfirm-labelstringstring | undefinedVerb-first label for the confirming action.
confirmVariantconfirm-variant"brand" | "danger"'brand'The confirming button's look, which the badge follows: brand (the default) asks a question beside the brand button; danger is the destructive treatment, the warning badge beside the danger button. The same prop, with the same two values, as on sui-confirm-popup.
detaildetailstringstring | undefinedSecondary fact under the message (audit trail, retention, reversibility).
headingheadingstringstring | undefinedDialog title (the reference title prop; renamed for the native attribute).
messagemessagestringstring | undefinedThe question or consequence, one sentence.
openopenbooleanfalseShows or hides the dialog. Mutable; the element closes itself unless the close event is canceled. Focus opens on Cancel, the least destructive action, as it does on the confirm popup, so Enter never confirms by accident. The element never opens itself, so there is no suiOpen; watch open. It is a sui-dialog underneath, modal the same way: everything outside it is inert, with no aria-modal, so toasts keep announcing and Tab reaches the toast stack; an element the page marks data-sui-keep-live stays usable. On close, focus returns as returnFocus says.
pendingpendingbooleanfalseThe confirmed action is in flight: the confirm button goes busy and Cancel is disabled, so a server-backed "Void invoice" cannot be fired twice. Set it from a suiConfirm listener that called preventDefault(), and clear open when the request resolves.
returnFocusreturn-focusstringstring | undefinedA selector for the control that takes focus when the dialog closes, resolved in the document the element sits in. Without it focus returns to the control that opened the dialog; when the confirmed action removed that control (a Delete button whose row went with the record), it goes to the nearest control in its place: the next row's, else the previous one's. A selector that matches nothing, or a disabled control, falls through to the same order.

Events

EventDetailReactVueDescription
suiCloseSuiCloseReason"cancel" | "close-button" | "escape" | "outside" | "pick" | "scrim" | "trigger"onSuiClose@sui-closeFires when Cancel, Escape or the scrim asks to close; detail is 'cancel' | 'escape' | 'scrim'. Cancelable: preventDefault() keeps it open and keeps focus inside. It does not bubble, as on every dismissible surface.
suiConfirmvoidonSuiConfirm@sui-confirmFires when the confirm button is pressed. Cancelable: preventDefault() keeps the dialog open, which is how a server-backed confirmation works: refuse it, set pending, and clear open when the request resolves.

Slots

SlotDescription
(default)Rich message content; used alongside or instead of the message prop.
detailRich detail line under the message; used alongside or instead of the detail prop.

Types

SuiCloseReason typeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also