Dialog

Modal dialogs for confirmations, forms, and complex interactions.

Basic Dialog

A simple dialog with form elements for collecting user input.

<.dialog :let={%{hide: hide}} id="x">
  <:trigger :let={attr}>
    <.button variant="secondary" type="button" {attr}>
      Open Dialog
    </.button>
  </:trigger>
  <form class="space-y-4">
    <.input type="text" name="name" placeholder="Name" />
    <.input type="password" name="name" placeholder="Password" />
    <div class="flex gap-2 justify-end">
      <.button variant="secondary" type="button" phx-click={hide}>
        Cancel
      </.button>
      <.button>
        Change
      </.button>
    </div>
  </form>
</.dialog>

Destructive Dialog

A dialog designed for destructive actions with warning styling and confirmation.

<.dialog :let={%{hide: hide}} size="sm" id="destroy">
  <:trigger :let={attr}>
    <.button variant="destructive" type="button" {attr}>
      Destroy Server
    </.button>
  </:trigger>
  <form class="space-y-4">
    <.input type="text" name="confirm" placeholder="Enter 'destroy server' to confirm" />
    <div class="flex gap-2 justify-end">
      <.button variant="secondary" type="button" phx-click={hide}>
        Cancel
      </.button>
      <.button variant="destructive">
        Destroy Server
      </.button>
    </div>
  </form>
</.dialog>

Notify Dialog

A dialog for showing notifications or status updates to the user.

<.dialog :let={%{hide: hide}} id="notify">
  <:trigger :let={attr}>
    <.button variant="secondary" type="button" {attr}>
      Notify Me
    </.button>
  </:trigger>
  <div class="-mt-1.5 mb-1 text-lg font-medium">Notifications</div>
  <div class="mb-6 text-base text-gray-600">
    You are all caught up. Good job!
  </div>
  <div class="flex justify-end gap-4">
    <.button phx-click={hide}>
      Close
    </.button>
  </div>
</.dialog>

Alert Dialog

An alert dialog with a destructive action, suitable for confirming critical operations.

<.dialog :let={%{hide: hide}} alert={true} id="alert-dialog">
  <:trigger :let={attr}>
    <.button variant="secondary" type="button" {attr}>
      Alert Dialog
    </.button>
  </:trigger>

  <div class="-mt-1.5 mb-1 text-lg font-medium">Delete Tweet</div>
  <div class="mb-6 text-base text-gray-600">
    Are you sure you want to delete this tweet? This action cannot be undone.
  </div>
  <div class="flex justify-end gap-4">
    <.button phx-click={hide} variant="secondary">
      Cancel
    </.button>

    <.button variant="destructive">
      Delete Tweet
    </.button>
  </div>
</.dialog>

Nested Dialog

A dialog that contains another dialog, demonstrating how to handle nested modal interactions.

<.dialog :let={%{hide: hide}} alert={true} id="nested-dialog">
  <:trigger :let={attr}>
    <.button variant="secondary" type="button" {attr}>
      Open Parent Dialog
    </.button>
  </:trigger>

  <div class="-mt-1.5 mb-1 text-lg font-medium">Parent Dialog</div>
  <div class="mb-6 text-base text-gray-600">
    This dialog contains another dialog.
  </div>
  <div class="flex justify-end gap-4">
    <.button phx-click={hide} variant="secondary">
      Close
    </.button>

    <.dialog :let={%{hide: hide}} alert={true} id="nested-dialog-child">
      <:trigger :let={attr}>
        <.button variant="secondary" type="button" {attr}>
          Open Child Dialog
        </.button>
      </:trigger>

      <div class="-mt-1.5 mb-1 text-lg font-medium">Child Dialog</div>
      <div class="mb-6 text-base text-gray-600">
        This is a nested dialog inside the parent.
      </div>
      <div class="flex justify-end gap-4">
        <.button phx-click={hide} variant="secondary">
          Close
        </.button>

        <.button variant="destructive">
          Action
        </.button>
      </div>
    </.dialog>
  </div>
</.dialog>

Custom Content Dialog

A dialog using the custom content slot for complete control over the dialog structure and styling.

<.dialog :let={%{hide: hide}} alert={false} id="alert-custom-content">
  <:trigger :let={attr}>
    <.button variant="secondary" type="button" {attr}>
      Custom Content
    </.button>
  </:trigger>

  <:content :let={{attr, %{hide: hide}}}>
    <div
      class={[
        "not-[hidden]:animate-in [hidden]:animate-out [hidden]:fade-out-0 not-[hidden]:fade-in-0 [hidden]:zoom-out-95 not-[hidden]:zoom-in-95",
        "bg-popover fixed top-[50%] left-[50%] z-50 grid w-full max-w-sm translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-sm"
      ]}
      {attr}
    >
      <div class="-mt-1.5 mb-1 text-lg font-medium">Custom Content Slot</div>
      <div class="mb-6 text-base text-gray-600">
        Using the custom content slot to completely customize the dialog appearance.
      </div>
      <div class="flex justify-end gap-4">
        <.button phx-click={hide} variant="secondary">
          Cancel
        </.button>

        <.button variant="destructive">
          Action
        </.button>
      </div>
    </div>
  </:content>
</.dialog>

Server Controlled with show={}

Dialog stays in DOM, visibility toggled via hidden attribute. Preserves form state and supports animations.

# Using show={@show_dialog} - Dialog stays in DOM, visibility toggled
# Preserves form state, animations work on show/hide
<.dialog id="server-dialog" show={@show_dialog} on_cancel={JS.push("close_dialog")}>
  ...
</.dialog>

Server Controlled with :if={}

Dialog mounted/unmounted from DOM. Form state resets on close, no exit animations.

# Using :if={@show_dialog} - Dialog mounted/unmounted from DOM
# Form state resets on close, no exit animations
<.dialog :if={@show_dialog} id="server-dialog-if" show={true} on_cancel={JS.push("close_dialog_if")}>
  ...
</.dialog>