A `Modal` pre-wired with a message and confirm/cancel actions, for "are you sure?" moments such as confirming a delete.

pnpm add @behivetech/molecules.confirm-dialog

You are viewing Confirm Dialog v0.2.8, snapshotted 2026-11-09. The latest is v0.2.10. Go to the latest →

registry/behivetech/molecules/confirm-dialog at v0.2.8, snapshotted 2026-11-09

confirm-dialog.tsx
1import { type ReactNode } from "react";
2import { getClassName } from "@bht-component-registry/class-names";
3import { Modal } from "@behivetech/atoms.modal";
4import { Button } from "@behivetech/atoms.button";
5import styles from "./confirm-dialog.module.scss";
6
7export interface ConfirmDialogProps {
8 /** Whether the dialog is open */
9 open: boolean;
10 /** Called when the dialog should close (cancel, confirm, backdrop click, Escape) */
11 onOpenChange: (open: boolean) => void;
12 /** Dialog title */
13 title: string;
14 /** Confirmation message/body content */
15 message: ReactNode;
16 /** Confirm button label; defaults to "Confirm" */
17 confirmLabel?: string;
18 /** Cancel button label; defaults to "Cancel" */
19 cancelLabel?: string;
20 /** Called when the confirm button is clicked, before the dialog closes */
21 onConfirm: () => void;
22 /** Renders the confirm button as a destructive action; defaults to "default" */
23 variant?: "default" | "danger";
24}
25
26/**
27 * A `Modal` pre-wired with a message and confirm/cancel actions, for "are you
28 * sure?" moments — e.g. confirming a delete.
29 */
30export const ConfirmDialog = ({
31 open,
32 onOpenChange,
33 title,
34 message,
35 confirmLabel = "Confirm",
36 cancelLabel = "Cancel",
37 onConfirm,
38 variant = "default",
39}: ConfirmDialogProps) => {
40 const [, getChildClass] = getClassName({
41 rootClass: "BHT__ConfirmDialog",
42 styles,
43 });
44
45 if (!open) return null;
46
47 return (
48 <Modal title={title} onClose={() => onOpenChange(false)}>
49 <p className={getChildClass("message")}>{message}</p>
50 <div className={getChildClass("actions")}>
51 <Button variant="ghost" onClick={() => onOpenChange(false)}>
52 {cancelLabel}
53 </Button>
54 <Button
55 variant={variant === "danger" ? "danger" : "primary"}
56 onClick={() => {
57 onConfirm();
58 onOpenChange(false);
59 }}
60 >
61 {confirmLabel}
62 </Button>
63 </div>
64 </Modal>
65 );
66};
confirm-dialog.module.scss
1// Content of a Material Design 3 basic dialog; the dialog itself is Modal.
2
3.BHT__ConfirmDialog {
4 &__message {
5 font-family: var(--md-sys-typescale-body-medium-font, var(--font-body));
6 font-size: var(--md-sys-typescale-body-medium-size, 0.875rem);
7 font-weight: var(--md-sys-typescale-body-medium-weight, 400);
8 line-height: var(--md-sys-typescale-body-medium-line-height, 1.25rem);
9 letter-spacing: var(--md-sys-typescale-body-medium-tracking, 0.0179em);
10 color: var(--md-sys-color-on-surface-variant, var(--color-text-muted));
11 margin: 0 0 var(--spacing-5, 1.25rem);
12 }
13
14 &__actions {
15 // Modal's panel is `surface-container-high`, where Button's default ghost
16 // label (`primary`) is 4.02:1 in the-behive's Dark tokens (8.43:1 in the
17 // defaults). `secondary` is 12.87:1 Dark and 5.40:1 Light there, so Cancel stays above the 4.5:1 small-text rule.
18 --button-label-color: var(--md-sys-color-secondary);
19
20 display: flex;
21 justify-content: flex-end;
22 gap: var(--spacing-3, 0.75rem);
23 }
24}
index.ts
1export { ConfirmDialog } from "./confirm-dialog.js";
2export type { ConfirmDialogProps } from "./confirm-dialog.js";
confirm-dialog.example.tsx
1import { useState } from "react";
2import { ConfirmDialog } from "./confirm-dialog.js";
3
4export const BasicConfirmDialog = () => {
5 const [open, setOpen] = useState(false);
6
7 return (
8 <>
9 <button type="button" onClick={() => setOpen(true)}>
10 Delete page
11 </button>
12 <ConfirmDialog
13 open={open}
14 onOpenChange={setOpen}
15 title="Delete page"
16 message='Are you sure you want to delete "About Us"? This cannot be undone.'
17 confirmLabel="Delete"
18 variant="danger"
19 onConfirm={() => console.log("confirmed delete")}
20 />
21 </>
22 );
23};