Components Alert dialog

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Alert dialog

One question, two answers, no way out without picking one. Built on native <dialog>: focus lands on Cancel, the backdrop ignores clicks, Escape cancels.
Confirm a choice

The default, and the one you reach for most: no media chip, primary confirm, nothing raising its voice. Focus lands on Cancel when it opens, Escape cancels, and clicking the backdrop does nothing. Relabel both buttons so each one names its outcome - Keep editing and Discard beat OK and Cancel every time.

Discard unsaved changes?

Your edits since the last save will be lost.

heex
<.alert_dialog
id="showcase-unsaved-changes"
title="Discard unsaved changes?"
description="Your edits since the last save will be lost."
confirm_label="Discard"
cancel_label="Keep editing"
>
<:trigger>
<.button color="gray" variant="outline">
<.icon name="hero-arrow-left" class="w-4 h-4 mr-1" /> Back to posts
</.button>
</:trigger>
</.alert_dialog>
A media chip beside the title

The media slot puts a 40px chip next to the question - an icon, an avatar, a thumbnail of the thing you are about to act on. Give icons the pc-alert-dialog__media-icon class so they inherit the chip's sizing; an img fills the chip on its own.

Sign out of every device?

You will need to sign in again on your phone, your tablet and any browser you have used this month.

heex
<.alert_dialog
id="showcase-media"
title="Sign out of every device?"
description="You will need to sign in again on your phone, your tablet and any browser you have used this month."
confirm_label="Sign out everywhere"
>
<:media>
<.icon name="hero-arrow-right-start-on-rectangle" class="pc-alert-dialog__media-icon" />
</:media>
<:trigger>
<.button color="gray" variant="outline">Sign out everywhere</.button>
</:trigger>
</.alert_dialog>
Destructive

variant="destructive" is the explicit opt-in, never the default: the chip takes the danger wash and a warning glyph, the confirm button moves to the danger ramp, and the description spells out exactly what is about to be lost.

Delete your account?

This permanently removes your account, your projects and every deployment attached to them. This cannot be undone.

heex
<.alert_dialog
id="showcase-delete-account"
variant="destructive"
title="Delete your account?"
description="This permanently removes your account, your projects and every deployment attached to them. This cannot be undone."
confirm_label="Delete account"
>
<:trigger>
<.button color="danger">Delete account</.button>
</:trigger>
</.alert_dialog>
Destructive with a summary

The body slot composes anything under the description - here a count and the invoice list, so the user can see exactly what the confirm button is going to take.

Delete 14 invoices?

These invoices leave your records immediately. Paid invoices stay in your Stripe account.

  • INV-2041 through INV-2054
  • 3 are already marked paid
  • Total value $18,420.00
heex
<.alert_dialog
id="showcase-bulk-delete"
variant="destructive"
title="Delete 14 invoices?"
description="These invoices leave your records immediately. Paid invoices stay in your Stripe account."
confirm_label="Delete 14 invoices"
>
<:trigger>
<.button color="danger" variant="outline">
<.icon name="hero-trash" class="w-4 h-4 mr-1" /> Delete selected
</.button>
</:trigger>
<ul class="pl-4 mt-1 space-y-1 list-disc marker:text-gray-400">
<li>INV-2041 through INV-2054</li>
<li>3 are already marked paid</li>
<li>Total value $18,420.00</li>
</ul>
</.alert_dialog>
Your own glyph on the destructive variant

The destructive variant ships a warning triangle; the media slot replaces it while keeping the danger wash, so the chip can name the thing rather than the severity.

Revoke this API key?

Any service still using this key stops working the moment you revoke it.

heex
<.alert_dialog
id="showcase-custom-media"
variant="destructive"
title="Revoke this API key?"
description="Any service still using this key stops working the moment you revoke it."
confirm_label="Revoke key"
>
<:media>
<.icon name="hero-key" class="pc-alert-dialog__media-icon" />
</:media>
<:trigger>
<.button color="danger" variant="outline">Revoke key</.button>
</:trigger>
</.alert_dialog>
Properties
Attribute Type Default Description
cancel_label string "Cancel" cancel button text
class any nil extra classes for the dialog element
confirm_label string "Continue" confirm button text
description string nil supporting copy explaining the consequence; wired to aria-describedby. Omit it when the inner_block carries the body
id* string unique id; open the dialog with open_alert_dialog/2
on_cancel {:struct, Phoenix.LiveView.JS} %Phoenix.LiveView.JS{ops: []} JS commands run when the user cancels, via the cancel button or Escape. The dialog closes afterwards
on_confirm {:struct, Phoenix.LiveView.JS} %Phoenix.LiveView.JS{ops: []} JS commands run when the user confirms, e.g. JS.push("delete_account"). The dialog closes afterwards
rest global
title* string the question being asked. Becomes the dialog's accessible name via aria-labelledby
variant string "default" default is the calm confirm - primary button, no media chip unless you pass one. destructive is the explicit opt-in: danger confirm button, and a danger-washed media chip carrying a warning glyph
one of: "default", "destructive"
:inner_block slot slot custom body content rendered below the description
:media slot slot an icon or image rendered in a chip beside the title. Give icons the pc-alert-dialog__media-icon class; an img fills the chip. The destructive variant supplies a warning glyph when this is empty; the default variant renders no chip unless you pass one
:trigger slot slot an opener rendered next to the dialog, pre-wired to open it