A reusable modal dialog component with robust features.
Features:
- Escape key to close
- Click backdrop to close
- Scrollable content area for long content
- Customizable width and height
- Title, content, and action slots
- Accessible with proper ARIA attributes
Examples
Basic usage:
<.modal show={@show_modal} on_close="close_modal">
<:title>Confirm Action</:title>
<p>Are you sure you want to proceed?</p>
<:actions>
<button class="btn btn-ghost" phx-click="close_modal">Cancel</button>
<button class="btn btn-primary" phx-click="confirm">Confirm</button>
</:actions>
</.modal>With icon in title:
<.modal show={@show_modal} on_close="close_modal">
<:title>
<.icon name="hero-exclamation-triangle" class="w-5 h-5 text-warning" />
Warning
</:title>
<p>This action cannot be undone.</p>
<:actions>
<button class="btn btn-ghost" phx-click="close_modal">Cancel</button>
<button class="btn btn-error" phx-click="delete">Delete</button>
</:actions>
</.modal>With scrollable content:
<.modal show={@show_modal} on_close="close_modal" max_height="60vh">
<:title>Long Content</:title>
<div class="space-y-2">
<%= for item <- @items do %>
<div class="p-2 border rounded"><%= item.name %></div>
<% end %>
</div>
<:actions>
<button class="btn btn-primary" phx-click="close_modal">Done</button>
</:actions>
</.modal>
Summary
Functions
Renders an info/alert modal with a single OK button.
Renders a confirmation modal with pre-styled warning/info messages.
Renders a modal dialog.
Functions
Renders an info/alert modal with a single OK button.
This is a convenience wrapper for displaying information or alerts that only require acknowledgment.
Attributes
show- Boolean to show/hide the modal (required)on_close- Event name when user closes (required)title- Modal title text (default: "Information")title_icon- Heroicon name for title (optional)type- Type of alert: :info, :success, :warning, :error (default: :info)message- Single message to display (alternative to messages list)messages- List of message tuples (alternative to single message)button_text- Text for close button (default: "OK")
Examples
Success alert:
<.alert_modal
show={@show_success}
on_close="close"
type={:success}
title="Success"
message="Your changes have been saved."
/>Error alert:
<.alert_modal
show={@show_error}
on_close="close"
type={:error}
title="Error"
message="Failed to save changes."
/>Attributes
show(:boolean) (required)on_close(:string) (required)title(:string) - Defaults tonil.title_icon(:string) - Defaults tonil.type(:atom) - Defaults to:info. Must be one of:info,:success,:warning, or:error.message(:string) - Defaults tonil.messages(:list) - Defaults to[].button_text(:string) - Defaults tonil.
Renders a confirmation modal with pre-styled warning/info messages.
This is a specialized modal for confirmation dialogs that may display multiple warnings or informational messages before an action.
Attributes
show- Boolean to show/hide the modal (required)on_confirm- Event name when user confirms (required unless show_confirm is false)on_cancel- Event name when user cancels (required)title- Modal title text (default: "Confirm")title_icon- Heroicon name for title (optional)title_icon_class- CSS classes for title icon (default: "w-5 h-5 text-primary")confirm_text- Text for confirm button (default: "Confirm")cancel_text- Text for cancel button (default: "Cancel")confirm_class- CSS classes for confirm button (default: "btn btn-primary")confirm_icon- Heroicon name for confirm button (optional)messages- List of message tuples: [{:warning | :info | :error | :success, "message"}, ...]prompt- Optional prompt text shown after messages (default: "Do you want to continue?")max_messages- Number of messages to show before scrolling (default: 5)confirm_disabled- Disable the confirm button (default: false)loading- Show loading spinner on confirm button (default: false)danger- Shorthand for destructive action styling (default: false)show_confirm- Whether to show the confirm button (default: true)closeable- Whether modal can be closed via backdrop/escape (default: true)
Slots
inner_block- Optional custom content to render after messages
Examples
Basic confirmation:
<.confirm_modal
show={@show_confirm}
on_confirm="do_action"
on_cancel="cancel_action"
title="Confirm Delete"
title_icon="hero-trash"
messages={[{:warning, "This will delete all data"}]}
confirm_text="Delete"
danger={true}
/>With loading state:
<.confirm_modal
show={@show_confirm}
on_confirm="save"
on_cancel="cancel"
loading={@saving}
confirm_disabled={@saving}
/>Error state (confirm disabled):
<.confirm_modal
show={@show_error}
on_confirm="retry"
on_cancel="close"
title="Error"
title_icon="hero-x-circle"
title_icon_class="w-5 h-5 text-error"
messages={[{:error, "Something went wrong. Please try again."}]}
confirm_disabled={true}
confirm_text="Retry"
/>Info-only modal (no confirm button):
<.confirm_modal
show={@show_info}
on_cancel="close"
title="Information"
title_icon="hero-information-circle"
title_icon_class="w-5 h-5 text-info"
messages={[{:info, "Your changes have been saved."}]}
show_confirm={false}
cancel_text="OK"
/>With custom content:
<.confirm_modal
show={@show_modal}
on_confirm="confirm"
on_cancel="cancel"
title="Review Changes"
>
<div class="mt-2">
<p>Custom content here...</p>
</div>
</.confirm_modal>Attributes
show(:boolean) (required)on_confirm(:string) - Defaults tonil.on_cancel(:string) (required)title(:string) - Defaults tonil.title_icon(:string) - Defaults tonil.title_icon_class(:string) - Defaults to"w-5 h-5 text-primary".confirm_text(:string) - Defaults tonil.cancel_text(:string) - Defaults tonil.confirm_class(:string) - Defaults tonil.confirm_icon(:string) - Defaults tonil.messages(:list) - Defaults to[].prompt(:string) - Defaults tonil.max_messages(:integer) - Defaults to5.confirm_disabled(:boolean) - Defaults tofalse.loading(:boolean) - Defaults tofalse.danger(:boolean) - Defaults tofalse.show_confirm(:boolean) - Defaults totrue.closeable(:boolean) - Defaults totrue.
Slots
inner_block
Renders a modal dialog.
Attributes
show- Boolean to show/hide the modal (required)on_close- Event name to send when modal should close (required)id- Optional ID for the modal elementmax_width- Maximum width class: "sm" … "7xl", "full" (default: "md")max_height- Maximum height for content area, e.g., "60vh", "400px" (default: "70vh")class- Additional CSS classes for the modal boxbackdrop_class- Additional CSS classes for the backdropcloseable- Whether the modal can be closed via backdrop/escape (default: true)placement-:center(default) or:end— a full-height drawer sliding in from the right edge, for forms opened over the page they belong toclose_guard-:inputmakes the dialog non-closeable (Esc/backdrop) on the client from the first keystroke in a submittable form inside it, until the server setscloseableback to true
Slots
title- Optional title slot, rendered in a header with proper stylinginner_block- Main content of the modal (required)actions- Optional actions slot, rendered in footer with proper alignment
Attributes
show(:boolean) (required)on_close(:string) (required)id(:string) - Defaults tonil.max_width(:string) - Defaults to"md". Must be one of"sm","md","lg","xl","2xl","3xl","4xl","5xl","6xl","7xl", or"full".max_height(:string) - Defaults to"70vh".class(:string) - Defaults to"".backdrop_class(:string) - Defaults to"".closeable(:boolean) - Defaults totrue.placement(:atom) -:centeris the classic centered box.:endis a drawer: a full-height sheet anchored to the end (right) edge that slides in from the side (daisyUI 5'smodal-end), for tall content such as a create/edit form opened over the page it belongs to — the page stays underneath, the sheet scrolls internally. The box takes the full width belowsm,max_widthabove it ("2xl"is a good form width);max_heightdoes not apply — the sheet always fills the viewport height. Defaults to:center. Must be one of:center, or:end.close_guard(:atom) -:inputarms thePkDialoghook's client-side guard: while a submittable form (form[phx-submit]) inside this dialog holds a field that differs from its rendered default, Esc and the backdrop are no-ops immediately, before the server has heard of the edit — the round trip (plus anyphx-debouncewindow) would otherwise let a quick Esc discard what was just typed. Recomputed on every keystroke and every server patch, never latched: once the server re-renders the form (with the value, or reset after a save) the guard lets go andcloseableis the only authority — so a dialog whose LiveView never reports dirtiness still closes. Filter/search forms (phx-changeonly,role="search",type="search") never count. Defaults tonil. Must be one ofnil, or:input.keep_in_dom(:boolean) - Whentrue, the<dialog>element is rendered into the DOM regardless of@show. Visibility is driven by thePkDialoghook (showModal/close) via thedata-showattribute. Suits modals whose inner content doesn't depend on context-conditional assigns (e.g. a strategy picker with a fixed list) and that benefit from instant client-side open — a trigger button can calldialog.showModal()locally without waiting for the server round-trip. Default is conditional rendering for backwards compat with consumers whose inner block crashes when@showis false (e.g. forms reading from anil@form). ID collision risk: kept-in-DOM modals persist across LV renders, so an auto-derived id (fromon_close) is far more likely to collide with a sibling modal sharing the same close event. Pass an explicitid=when usingkeep_in_domto be safe. Defaults tofalse.- Global attributes are accepted. Extra attributes for the
<dialog>element.phx-value-*ones travel with the close event thePkDialoghook pushes (Esc / backdrop), the same way they would on aphx-click— a stacked host stamps its frame identifier there.
Slots
titleinner_block(required)actions