Confirmation Modals
Pattern for confirming destructive or high-risk actions before they execute.
Overview
Critical actions in the platform should usually require explicit confirmation before they run.
This pattern exists to prevent accidents for actions that are:
- destructive
- difficult to undo
- bulk-affecting
- externally visible
- high-risk enough that an accidental click would be costly
In the platform, confirmation is generally implemented with a modal rather than a browser confirm dialog or a lightweight inline prompt.
When Confirmation Fits
Good fit:
- deleting content, entities, or relationships
- changing a setting that resets or removes important configuration
- cancelling, revoking, or expiring something important
- actions that notify external users or affect multiple records
- bulk actions with meaningful consequences
Usually not needed:
- reversible low-risk toggles
- standard save actions
- navigation
- harmless UI preferences
Over-confirming everything creates friction, so use this pattern deliberately.
Overlay rule:
- a confirmation modal must not open on top of another modal
- a panel must not open on top of a modal just to continue a confirmation flow
If the user is already in a modal, keep the confirmation inside that same modal flow or reconsider whether the first surface should have been a panel instead.
Choose The Right Abstraction
| Use this | When it fits |
|---|---|
ConfirmActionModal | The feature already owns modal state, or the flow needs custom close behavior or extra modal content |
ConfirmActionButton | A single button should open confirmation inline in a form, footer, or panel action area |
Menu.ItemWithConfirmation | The action originates from a Menu or Panel.Menu and should confirm before executing |
Modal | The flow is not really a simple confirmation and needs a richer custom dialog or multi-step interaction |
Raw Confirmation Modal
Use raw ConfirmActionModal when state is already feature-owned:
<ConfirmActionModal
isOpen={isConfirmOpen}
handleClose={() => setIsConfirmOpen(false)}
onConfirm={handleDeleteCollection}
title="Delete Collection"
description={`Are you sure you want to delete "${collection.name}"? This will remove the collection but keep the assets.`}
buttonLabel="Yes, delete"
isPending={isDeleting}
/>This is common when the confirmation must coordinate with local screen state or multiple possible follow-up actions.
Inline Confirmation Button
Use ConfirmActionButton when the trigger and confirmation belong together:
<ConfirmActionButton
title={`Delete "${event.subject}"?`}
description="This event will be deleted permanently and removed from all calendars."
variant="destructive"
onConfirm={handleDeleteEvent}
isPending={isDeletingEvent}
>
Delete Event
</ConfirmActionButton>This keeps the triggering action and the confirmation flow in one place.
Menu-Driven Confirmation
Use Menu.ItemWithConfirmation when the action starts in a menu:
<Panel.Menu.ItemWithConfirmation
label="Delete Location"
icon="TrashCan02"
variant="destructive"
onSelect={handleDeleteLocation}
confirmTitle="Delete location?"
confirmDescription="This action cannot be undone."
confirmButtonText="Delete"
/>Prefer this over manually opening a sibling modal unless the flow genuinely needs special handling.
Copy Guidelines
Confirmation copy should make the consequence obvious:
- title should name the action clearly
- description should say what will happen and whether it can be undone
- confirm button text should match the action, such as
Delete,Remove Access, orChange Event
Avoid vague copy like Are you sure? with no context.
Also avoid delete-focused default copy when the action is not deletion.
Async And Error Handling
ConfirmActionModal awaits onConfirm.
That means:
- success closes the modal
- thrown errors keep the modal open
onSuccessonly runs after a successful confirm
If the flow can fail, make sure the feature surfaces the error clearly and manages pending state intentionally.
Brand Dashboard Usage Patterns
Representative usages:
| Pattern | Path | Notes |
|---|---|---|
| Configuration reset confirmation | fsai/apps/brand-dashboard/src/pages/Workflows/components/config-panel/TriggerConfig.tsx | Good example of a destructive action that is not deletion. |
| Panel menu destructive action | fsai/apps/brand-dashboard/src/modules/locations/components/LocationPanel/LocationPanel.tsx | Uses Panel.Menu.ItemWithConfirmation for destructive entity actions. |
| Actions menu abstraction | fsai/apps/brand-dashboard/src/components/ActionsMenu/ActionsMenu.tsx | Automatically routes items with showsConfirmationModal into Menu.ItemWithConfirmation. |
| Inline form action | fsai/apps/brand-dashboard/src/pages/Home/Calendar/CalendarEventInformation/CalendarEventForm.tsx | ConfirmActionButton inside a form action row. |
| Manual menu + confirm modal | fsai/apps/brand-dashboard/src/modules/library/components/CollectionDropdown.tsx | Shows the fallback pattern when menu-driven confirmation needs local modal state. |
| Content deletion | fsai/apps/brand-dashboard/src/pages/Learning/components/CoursesTab.tsx | Straightforward controlled confirmation modal. |
Relationship To Other Docs
- See the
ConfirmActionModalcomponent page for the exact API. - See the
Menucomponent page for menu-specific confirmation item details. - See the
Modalcomponent page for richer dialogs that go beyond a simple confirmation step.
Guidelines
- Do confirm destructive or high-risk actions with a modal
- Do use
Menu.ItemWithConfirmationwhen the action originates from a menu - Do make the consequence explicit in the title, description, and confirm button label
- Do reserve generic
Modalfor cases that are more complex than a simple yes/no confirmation - Don't add confirmation friction to low-risk reversible actions
- Don't stack a confirmation modal on top of another modal
- Don't rely on delete-focused default copy for unrelated actions