Alert Dialog
A dialog that requires a user response to proceed.
Last choice: none yet
Codeapp/demos/public/alert-dialog.tsx
import { clientEntry, on, type Handle } from 'remix/component'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@stemsui/registry/ui/alert-dialog.tsx'
import { buttonVariants } from '@stemsui/registry/ui/button.tsx'
export const AlertDialogDemo = clientEntry(
import.meta.url,
function AlertDialogDemo(handle: Handle) {
let result = 'none yet'
return () => (
<div class="flex flex-col items-center gap-4">
<AlertDialog
onOpenChange={(open, details) => {
if (!open && details.reason !== 'close-press') {
result = `dismissed (${details.reason})`
void handle.update()
}
}}
>
<AlertDialogTrigger class={buttonVariants({ variant: 'outline' })}>
Show Dialog
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete your account and remove
your data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
mix={on('click', () => {
result = 'cancelled'
void handle.update()
})}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction
closes
mix={on('click', () => {
result = 'continued'
void handle.update()
})}
>
Continue
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<p class="text-sm text-muted-foreground" data-testid="alert-dialog-result">
Last choice: <span class="font-medium text-foreground">{result}</span>
</p>
</div>
)
},
)
Installation
npx shadcn add @stemsui/alert-dialogRequires the one-time setup. Behavior: stems-ui/alert-dialog.
API reference
The styled component is built on the headless module below: its props mirror these options, and the same events and data attributes apply.
stems-ui/alert-dialog
alert-dialog is a dialog that requires a response. It is always modal (showModal()) and has
role="alertdialog". Outside presses never close it, but Escape does. It is built on the same
engine as stems-ui/dialog.
Base UI equivalent: Alert Dialog.
Usage
import * as alertDialog from 'stems-ui/alert-dialog'
function DeleteAccount() {
return () => (
<alertDialog.Context>
<button mix={alertDialog.trigger()}>Delete account</button>
<dialog class="m-auto open:grid backdrop:bg-black/10" mix={alertDialog.popup()}>
<h2 mix={alertDialog.title()}>Are you absolutely sure?</h2>
<p mix={alertDialog.description()}>This action cannot be undone.</p>
<button mix={alertDialog.close()}>Cancel</button>
<button mix={alertDialog.close()}>Delete</button>
</dialog>
</alertDialog.Context>
)
}
stems-ui/alert-dialog
The parts, options, behavior, and styling hooks are the same as in
stems-ui/dialog, with these differences:
Contexttakesopen,defaultOpen,onOpenChange(open, details),onOpenChangeComplete(open), andactionsRef. It has nomodalordisablePointerDismissalprops: the alert dialog is always modal, and outside presses never dismiss it.popup(options?)setsrole="alertdialog"andaria-modal="true". The default initial focus is the first tabbable element, so put the least destructive action (Cancel) first.- Outside presses, whether on
::backdropor on abackdrop()element, do nothing. They also stop the dismiss walk, so a dialog underneath stays open. Escape closes the alert dialog (reason'escape-key'). onAlertDialogChange(handler)is the listener mixin forAlertDialogChangeEvent(stems:alert-dialog-change, bubbles, dispatched from the popup, hasopenandreason).- Exported types are prefixed
AlertDialogā¦, for exampleAlertDialogPopupOptionsandAlertDialogChangeDetails.
An alert dialog nested in a dialog (or a dialog nested in an alert dialog) takes part in the
same nesting: data-nested, data-nested-dialog-open, and --nested-dialogs.
Detached triggers
alertDialog.createHandle() works like dialog.createHandle():
pass the handle to <alertDialog.Context handle> and to alertDialog.trigger({ handle, payload }) anywhere in the document; the Context's children may be a function of
{ payload }. The handle has isOpen, open(triggerId?), openWithPayload(payload), and
close(), and the Context takes triggerId / defaultTriggerId.
Styling hooks
| Part | Attribute / variable |
|---|---|
trigger() | aria-expanded, data-popup-open, data-disabled |
popup() | role="alertdialog", :open, data-open / data-closed, data-starting-style / data-ending-style, data-nested, data-nested-dialog-open, --nested-dialogs |
backdrop() | hidden, data-open / data-closed, data-starting-style / data-ending-style |
close() | data-disabled |