Confirmation with confirm/cancel actions.

The ConfirmationModal component is a specialized modal designed for simple confirmation dialogs. It provides a streamlined way to ask users for confirmation before proceeding with an action, without the need to manually manage modal state or construct the modal UI.

Built on top of the Modal Manager, ConfirmationModal handles all the complexity of opening, closing, and managing the modal's state, allowing you to focus on the confirmation logic.

Use a ConfirmationModal when you need to:

  • Confirm destructive actions: Deleting items, removing users, or any action that cannot be easily undone
  • Verify important decisions: Actions that will have significant impact, like publishing campaigns or exporting data
  • Prevent accidental clicks: Adding an extra step before committing to an action

Some guidelines:

  • Keep it simple: For complex forms or multi-step processes, use the regular Modal component instead
  • Be specific with labels: Use action-specific labels like "Delete", "Publish", or "Remove" instead of generic "OK" or "Yes"
  • Be clear and concise: The message should clearly explain what will happen if the user confirms, especially for destructive actions
  • Use danger styling appropriately: Only use isDanger for truly destructive or irreversible actions

Importing

import {
	ConfirmationModal,
	useConfirmationModal,
} from '@customerio/pluma-components/react';

Usage

The simplest way to use a confirmation modal is with the useConfirmationModal hook, which returns a function that:

  • Accepts a configuration object with the modal's content and behavior
  • Opens the modal automatically when called
  • Returns a promise that resolves to true if the user confirmed, or false if they cancelled
import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleDelete = async () => {
		const isConfirmed = await confirm({
			title: 'Delete item?',
			message: 'Are you sure you want to delete this item?',
		});

		if (isConfirmed) {
			console.log('Item deleted');
		} else {
			console.log('Deletion cancelled');
		}
	};

	return (
		<Button onClick={handleDelete} isDanger>
			Delete Item
		</Button>
	);
}

Customizing labels

You can customize the button labels to make the action more explicit and contextual.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handlePublish = async () => {
		const isConfirmed = await confirm({
			title: 'Publish campaign?',
			message: 'This campaign will be sent to 10,000 customers.',
			confirmLabel: 'Publish',
			cancelLabel: 'Go back',
		});

		if (isConfirmed) {
			console.log('Campaign published');
		}
	};

	return (
		<Button onClick={handlePublish} variant="primary">
			Publish Campaign
		</Button>
	);
}

Danger actions

For destructive or irreversible actions, use the isDanger prop to style the confirm button in red, making it clear that this is a potentially dangerous action.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleDelete = async () => {
		const isConfirmed = await confirm({
			title: 'Delete workspace?',
			message: 'This action cannot be undone. All data will be permanently deleted.',
			confirmLabel: 'Delete',
			cancelLabel: 'Keep',
			isDanger: true,
		});

		if (isConfirmed) {
			console.log('Workspace deleted');
		}
	};

	return (
		<Button onClick={handleDelete} isDanger>
			Delete Workspace
		</Button>
	);
}

Non-dismissable modals

By default, confirmation modals can be dismissed by pressing Escape, clicking the overlay, or clicking the close button. For critical actions where the user must make an explicit choice, set isDismissable to false.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleCriticalAction = async () => {
		const isConfirmed = await confirm({
			title: 'Critical action required',
			message: 'You must choose an option to continue. This modal cannot be dismissed.',
			isDismissable: false,
		});

		if (isConfirmed) {
			console.log('Action confirmed');
		} else {
			console.log('Action cancelled');
		}
	};

	return (
		<Button onClick={handleCriticalAction} variant="primary">
			Start Critical Action
		</Button>
	);
}

Confirmation phrase

For the most critical destructive actions, you can require the user to type a specific phrase before the confirm button becomes enabled. This adds deliberate friction, ensuring the user fully understands the consequences before proceeding.

This pairs well with isDanger: true and isDismissable: false for maximum protection.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleDelete = async () => {
		const isConfirmed = await confirm({
			title: 'Delete workspace?',
			message: 'This action cannot be undone. All data, campaigns, and history will be permanently deleted.',
			confirmLabel: 'Delete',
			cancelLabel: 'Cancel',
			isDanger: true,
			isDismissable: false,
			confirmationPhrase: 'delete my workspace',
		});

		if (isConfirmed) {
			console.log('Workspace deleted');
		}
	};

	return (
		<Button onClick={handleDelete} isDanger>
			Delete Workspace
		</Button>
	);
}

Custom message content

For more complex confirmation messages, you can pass custom components instead of plain strings to the message option. This allows you to include formatted text, links, lists, or any other custom content in your confirmation modal.

import { Button, Paragraph, Strong, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleBulkDelete = async () => {
		const itemCount = 42;

		const isConfirmed = await confirm({
			title: 'Delete items?',
			message: (
				<Paragraph>
					This action will affect <Strong>{itemCount}</Strong> items in your workspace.
				</Paragraph>
			),
			confirmLabel: 'Delete',
			cancelLabel: 'Cancel',
			isDanger: true,
		});

		if (isConfirmed) {
			console.log('Items deleted');
		}
	};

	return (
		<Button onClick={handleBulkDelete} isDanger>
			Delete Items
		</Button>
	);
}

When using custom message components:

  • React: Pass any React node to the message option
  • Ember: Pass a component to the message option. The component will receive all modal data via @data, including any custom properties you pass to the modal
  • Keep accessibility in mind - ensure your custom content is readable and properly structured
  • The custom component is rendered within the modal body, so it will inherit the modal's spacing and layout

Using with the Modal Manager directly

While useConfirmationModal is the recommended approach, you can also use the ConfirmationModal component directly with the Modal Manager for more control over the modal's behavior.

import { useModals, ConfirmationModal } from '@customerio/pluma-components/react';

function MyComponent() {
	const modals = useModals();

	const handleDelete = async () => {
		const id = modals.open(ConfirmationModal, {
			title: 'Delete item?',
			message: 'Are you sure you want to delete this item?',
			confirmLabel: 'Delete',
			isDanger: true,
		});

		const result = await modals.waitForClose(id);

		if (result.reason === 'confirm') {
			// User clicked the confirm button
			console.log('Confirmed with data:', result.data); // result.data will be true
		} else if (result.reason === 'close') {
			// User clicked cancel, ESC, or overlay
			console.log('Cancelled with data:', result.data); // result.data will be false
		}
	};

	return <Button onClick={handleDelete}>Delete</Button>;
}

Callbacks

You can provide optional callbacks that will be invoked when the user confirms or cancels the action. These callbacks can be synchronous or return a promise for async operations. While a callback is executing, the modal buttons are disabled and the modal cannot be dismissed.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleDelete = async () => {
		const isConfirmed = await confirm({
			title: 'Delete item?',
			message: 'This action cannot be undone.',
			onConfirm: async () => {
				// Perform the deletion
				await deleteItemAPI();
				console.log('Item deleted successfully');
			},
			onCancel: () => {
				console.log('Deletion cancelled');
			},
		});

		console.log('Modal closed, confirmed:', isConfirmed);
	};

	return (
		<Button onClick={handleDelete} isDanger>
			Delete Item
		</Button>
	);
}

Important notes about callbacks:

  • Callbacks can be sync or async (return a promise)
  • While a callback is running, both buttons are disabled and the modal cannot be dismissed
  • onConfirm is called when the user clicks the confirm button
  • onCancel is called when the user clicks the cancel button, presses ESC, or clicks the overlay
  • The promise returned by confirm() won't resolve until the callback completes

Closing on unmount

Since ConfirmationModal is built on the Modal Manager, you can pass additional configuration options as a second argument to control modal behavior. See the Modal Manager documentation for all available options.

import { Button, useConfirmationModal } from '@customerio/pluma-components/react';

export default function Example() {
	const confirm = useConfirmationModal();

	const handleDelete = async () => {
		const isConfirmed = await confirm(
			{
				title: 'Delete item?',
				message: 'Are you sure you want to delete this item?',
			},
			{ shouldCloseOnUnmount: true }
		);

		if (isConfirmed) {
			console.log('Item deleted');
		}
	};

	return <Button onClick={handleDelete}>Delete</Button>;
}

Configuration options

The confirmation modal accepts the following configuration options:

table
OptionTypeDefaultDescription
titlestring(required)The title displayed in the modal header
messagestring or Component(required)The message displayed in the modal body. Can be a plain string or a custom component for more complex content.
confirmLabelstring"Confirm"The label for the confirm button
cancelLabelstring"Cancel"The label for the cancel button
isDangerbooleanfalseWhether to style the confirm button as a danger action (red)
isDismissablebooleantrueWhether the modal can be dismissed via ESC key, overlay click, or close button
onConfirm() => void | Promise<void>undefinedOptional callback invoked when the user confirms. Can be async.
onCancel() => void | Promise<void>undefinedOptional callback invoked when the user cancels or dismisses the modal. Can be async.
confirmationPhrasestringundefinedA phrase the user must type exactly to enable the confirm button. Use for destructive actions where extra friction is needed.

Return value

The useConfirmationModal hook returns a function that, when called, returns a promise that resolves to:

  • true - if the user clicked the confirm button
  • false - if the user clicked cancel, pressed ESC, clicked the overlay, or clicked the close button

When using the Modal Manager directly, the waitForClose promise resolves to an object with:

  • reason: Either 'confirm' or 'close'
  • data: true if confirmed, false if cancelled