Confirm Dialog
Destructive confirmation (Radix AlertDialog) over a hazard-striped overlay: red 'Danger zone' band, consequences list, and a type-the-name field that arms the delete button; async pending state.
brutalist/ui/confirm-dialogSource
"use client";
import { useId, useState, type ReactNode } from "react";
import { AlertDialog } from "radix-ui";
import { cn } from "@/lib/utils";
export interface ConfirmDialogProps {
trigger: ReactNode;
title: string;
description: string;
/** The user must type this exact text to enable the confirm button. */
confirmText?: string;
confirmLabel?: string;
cancelLabel?: string;
consequences?: string[];
onConfirm?: () => void | Promise<void>;
da?: string;
}
export function ConfirmDialog({
trigger,
title,
description,
confirmText,
confirmLabel = "Delete",
cancelLabel = "Cancel",
consequences = [],
onConfirm,
da = "brutalist",
}: ConfirmDialogProps) {
const [typed, setTyped] = useState("");
const [pending, setPending] = useState(false);
const [open, setOpen] = useState(false);
const inputId = useId();
const armed = !confirmText || typed === confirmText;
return (
<AlertDialog.Root
open={open}
onOpenChange={(o) => {
setOpen(o);
setTyped("");
}}
>
<AlertDialog.Trigger asChild>{trigger}</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay data-da={da} className="fixed inset-0 z-50 bg-da-overlay [background-image:repeating-linear-gradient(-45deg,transparent_0_18px,rgb(0_0_0/0.25)_18px_22px)]" />
<AlertDialog.Content
data-da={da}
className="da-stroke fixed top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 bg-da-surface text-da-surface-fg shadow-da-lg outline-none"
>
<div className="da-stroke-b flex items-center gap-3 bg-da-danger px-5 py-3 text-da-danger-fg">
<span aria-hidden className="da-stroke grid size-8 place-items-center bg-da-surface font-da-display text-da-surface-fg">
!
</span>
<span className="font-da-mono text-xs font-bold tracking-da-label uppercase">Danger zone</span>
</div>
<div className="p-6">
<AlertDialog.Title className="font-da-display text-2xl leading-tight tracking-da-display uppercase">{title}</AlertDialog.Title>
<AlertDialog.Description className="mt-3 text-da-muted-fg">{description}</AlertDialog.Description>
{consequences.length > 0 && (
<ul className="da-stroke mt-5 divide-y-0 bg-da-surface-2">
{consequences.map((c, i) => (
<li key={c} className={cn("flex gap-3 px-4 py-2.5 text-sm font-bold", i > 0 && "da-stroke-t")}>
<span aria-hidden>✕</span>
{c}
</li>
))}
</ul>
)}
{confirmText && (
<div className="mt-5 grid gap-2">
<label htmlFor={inputId} className="text-sm">
Type <code className="da-stroke bg-da-muted px-1 font-da-mono font-bold">{confirmText}</code> to confirm
</label>
<input
id={inputId}
value={typed}
onChange={(e) => setTyped(e.target.value)}
autoComplete="off"
spellCheck={false}
className="da-focus da-stroke bg-da-input px-3 py-2.5 font-da-mono font-bold"
/>
</div>
)}
</div>
<div className="da-stroke-t flex flex-col-reverse gap-3 bg-da-surface-2 p-5 sm:flex-row sm:justify-end">
<AlertDialog.Cancel className="da-focus da-stroke bg-da-surface px-5 py-3 font-bold text-da-surface-fg">{cancelLabel}</AlertDialog.Cancel>
<AlertDialog.Action
disabled={!armed || pending}
onClick={async (e) => {
if (!onConfirm) return;
e.preventDefault();
setPending(true);
try {
await onConfirm();
setOpen(false);
} finally {
setPending(false);
}
}}
className="da-focus da-stroke bg-da-danger px-5 py-3 font-bold text-da-danger-fg shadow-da-sm hover:translate-x-[3px] hover:translate-y-[3px] hover:shadow-none disabled:cursor-not-allowed disabled:opacity-40 disabled:shadow-none"
>
{pending ? "Deleting…" : confirmLabel}
</AlertDialog.Action>
</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
);
}
export default ConfirmDialog;
modules/brutalist/ui/confirm-dialog/index.tsx
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger* | ReactNode | — | Element that opens the dialog. |
| title / description* | string | — | What will happen. |
| confirmText | string | — | Exact text to type before confirming (omit to skip). |
| confirmLabel / cancelLabel | string | "Delete" / "Cancel" | Button labels. |
| consequences | string[] | — | List of what gets lost. |
| onConfirm | () => void | Promise<void> | — | Awaited before closing (shows pending). |
| da | string | "brutalist" | DA scope for the portal. |
Other modal variants in Brutalist
Drawer
Side sheet (Radix Dialog) sliding in from the right or left in stepped motion: yellow title band with close square, scrollable body for forms, ruled footer actions.
Modal
Accessible dialog on Radix Dialog: colored title band (primary or danger), close button, scrollable body, footer action bar. Re-applies the DA scope inside the portal. Stepped drop-in entrance.
Onboarding Modal
Multi-step onboarding dialog: a colored visual panel (giant step number by default) next to title, description, segmented progress bar and Back/Next buttons; resets on open.
Modal in other art directions
Bottom Sheet
Mobile-style bottom sheet (Radix Dialog): heavily frosted panel sliding up with a grab handle, title and description; becomes a floating centered card on wider screens.
Dropdown Menu
Frosted dropdown menu (Radix DropdownMenu) built from an items array: labels, icons, shortcuts, separators, checkbox items, nested submenus and a danger item.
Modal
Frosted glass sheet dialog (Radix): 40px backdrop blur, optional icon bubble, rise-and-pop entrance with overshoot, blurred overlay, round close button and footer actions.
Share Dialog
Glass share dialog: invite-by-email field with role select, people-with-access list, a general access switch (invited only / anyone with link) and a copy-link button with confirmation.
Command Palette
⌘K command palette (Radix Dialog): search input, results filtered as you type and grouped (actions, incidents, schedules), icons and shortcuts, arrow-key highlight, Enter to run, footer hints.
Modal
Radix-based dialog with fade + subtle scale entrance, optional icon, title/description, scrollable body and a tinted footer action bar. Re-applies the DA scope inside the portal.
Popover Card
Anchored popover (Radix Popover) with arrow, optional title + close button and any content — quick forms (snooze), definitions or details. Collision-aware placement.
Sheet
Side sheet (Radix Dialog) sliding in from the right or left over a dimmed overlay: title and description header with close button, scrollable body and footer actions.
Command Dialog
Command palette (Radix Dialog + listbox): a square panel near the top with a large borderless search, grouped results under mono headings, arrow-key navigation with an inverted active row and shortcut hints.
Drawer
Right side panel (Radix Dialog) sliding in over a dimmed page: mono top bar with label and “Close”, large title, scrollable body with ruled rows and a sticky footer.
Lightbox
Full-screen image viewer (Radix Dialog) on ink (stays dark in dark mode): the image frame centered, mono “03 / 04” counter and caption, Prev/Next text buttons and arrow-key navigation, thumbnails strip.
Modal
Square dialog (Radix): a mono label + text “Close” in the top bar, a full-ink rule, a large title, body and right-aligned actions; opens with a quick fade and 8px rise.
Confirm Dialog
Destructive confirmation (Radix AlertDialog): red warning icon, title, consequence text, optional “type VOID to confirm” guard, cancel + red confirm.
Modal
Corporate dialog (Radix): white card with header (title, description, close), divided scrollable body and a tinted footer bar for actions; quick fade + slight scale.
Payment Modal
Pay-invoice dialog: amount summary, card/bank method radio cards, method-specific fields, secure note and a pay button that switches to a success state.
Side Sheet
Detail drawer (Radix Dialog) sliding in from the right: full-height bordered panel with sticky header, scrollable body and footer actions — for record details.
Bottom Sheet
Bottom sheet (Radix Dialog): slides up from the bottom with a grab handle and very rounded top corners; becomes a centered card from sm up.
Confirm Dialog
Gentle confirmation (Radix AlertDialog): a wilting-leaf illustration in a clay circle, serif question, kind consequence text, a sage “keep” action and a quiet clay confirm.
Modal
Soft dialog (Radix): large-radius cream card that rises gently with a slight overshoot, serif title, round close button and pill actions.
Welcome Modal
Onboarding dialog: an illustrated tinted header (sprout growing with each step), serif title and copy, leaf-shaped step dots, Back/Next pills and “Let’s begin” on the last step.
Celebrate Modal
“Week complete!” celebration dialog: pastel confetti shapes burst from the center, trophy emoji, friendly copy, three pastel stat tiles and one pill button.
Confirm Sheet
Destructive confirmation shown as a bottom sheet on mobile and a centered card on desktop: big emoji in a red-tint circle, gentle copy, red confirm with pending state and a soft cancel.
Modal
Soft modal (Radix Dialog): big rounded white card popping in with a small bounce, pastel emoji circle, round close button, body and pill footer actions.
Popover Menu
Rounded dropdown menu (Radix DropdownMenu) with emoji items, keyboard hints, soft highlight, separators and a red danger item; pops in with a bounce.