Feedback Toast & Flash

Toast & Flash

Toast notification system with animations, positioning, and auto-dismiss.

PUI's Flash system provides a full-featured toast notification system with stacking, auto-dismiss, multiple positions, and LiveComponent support. It's built as an enhanced replacement for Phoenix's built-in flash messages.

Import

use PUI
# or
import PUI.Flash

Basic Usage

Add the flash_group component to your layout to enable flash messages:

<.flash_group flash={@flash} />

Sending Flash Messages

Send flash messages from your LiveView event handlers:

def handle_event("save", _params, socket) do
PUI.Flash.send_flash("Changes saved successfully!")
{:noreply, socket}
end

Phoenix Preset Toasts

Phoenix flash keys such as :success, :error, :info, and :warning are automatically rendered as compact pill-shaped toasts with a type-colored icon:

def handle_event("save", _params, socket) do
{:noreply, put_flash(socket, :success, "Changes saved!")}
end
def handle_event("delete", _params, socket) do
{:noreply, put_flash(socket, :error, "Could not delete item")}
end
def handle_event("warn", _params, socket) do
{:noreply, put_flash(socket, :warning, "Session expires soon")}
end
def handle_event("notify", _params, socket) do
{:noreply, put_flash(socket, :info, "New update available")}
end

You can also trigger the same preset toast style through send_flash:

PUI.Flash.send_flash(%PUI.Flash.Message{
type: :success,
message: "Connected!"
})

Preset toasts use a dark pill-shaped container, truncate to one line, and show a visible close button. Messages sent without a preset type keep the standard flash UI.

With Options

# Success message
PUI.Flash.send_flash(%PUI.Flash.Message{
type: :info,
message: "Item created!",
duration: 3000
})
# Error message
PUI.Flash.send_flash(%PUI.Flash.Message{
type: :error,
message: "Failed to save"
})

Custom Content

Send HEEx content in flashes. When message is a HEEx template, the custom markup overrides the preset toast styling. Plain-string messages with a preset type still render as the compact built-in toast with a type-colored icon:

PUI.Flash.send_flash(%PUI.Flash.Message{
type: :success,
message: ~H|<div class="flex items-center gap-2">
<.icon name="hero-check-circle" class="size-5" />
<span>Success!</span>
</div>|
})

Custom Flash with Async Update

You can send a custom flash with rich HEEx content and update it later by ID. This is useful for showing progress and then replacing it with a result:

def handle_event("dispatch_ping", _params, socket) do
server = socket.assigns.server
message = ~H"""
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="animate-spin text-foreground size-5"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 6l0 -3" />
<path d="M16.25 7.75l2.15 -2.15" />
<path d="M18 12l3 0" />
<path d="M16.25 16.25l2.15 2.15" />
<path d="M12 18l0 3" />
<path d="M7.75 16.25l-2.15 2.15" />
<path d="M6 12l-3 0" />
<path d="M7.75 7.75l-2.15 -2.15" />
</svg>
<div>Connecting to server...</div>
</div>
"""
PUI.Flash.send_flash(%PUI.Flash.Message{
id: "ping-#{server.id}",
message: message,
duration: -1
})
parent = self()
Task.async(fn ->
message =
case perform_ping(server.id) do
{:ok, %{status: :up}} ->
~H"""
<div class="flex items-center gap-2">
<.icon name="hero-check-circle" class="size-6 text-green-600" />
<div>Server connected</div>
</div>
"""
_ ->
~H"""
<div class="flex items-center gap-2">
<.icon name="hero-x-circle" class="size-6 text-red-600" />
<div>Server unreachable</div>
</div>
"""
end
PUI.Flash.update_flash(parent, %PUI.Flash.Message{
id: "ping-#{server.id}",
message: message,
duration: 5
})
end)
{:noreply, socket}
end

Set duration: -1 to keep the flash open until you explicitly update or dismiss it.

When message is a HEEx template, the custom markup overrides the preset toast styling. Plain-string messages with a preset type still render as the compact built-in toast with a type-colored icon.

Interactive Demo

Custom Async Flash

Send a custom flash with a spinner, then update it after a simulated async operation completes.

Positioning

Flash groups support six positions:

<.flash_group flash={@flash} position="top-center" />
<.flash_group flash={@flash} position="top-left" />
<.flash_group flash={@flash} position="top-right" />
<.flash_group flash={@flash} position="bottom-center" />
<.flash_group flash={@flash} position="bottom-left" />
<.flash_group flash={@flash} position="bottom-right" />

Interactive Demo

Send Toast

Position

Position: top-center. Count: 0

Auto-Dismiss

Control auto-dismiss timing (in milliseconds):

<!-- Dismiss after 3 seconds -->
<.flash_group flash={@flash} auto_dismiss={3000} />
<!-- Dismiss after 10 seconds -->
<.flash_group flash={@flash} auto_dismiss={10000} />

Message Limit

Limit the number of visible messages:

<.flash_group flash={@flash} limit={3} />

Live Component Mode

Enable LiveComponent mode for richer flash management:

<.flash_group flash={@flash} live={true} />

Closeable

Control whether flash messages show a close button:

<.flash_group flash={@flash} show_close={false} />

API Reference

FlashGroup Attributes

Name Type Default Description
flash map required The flash map from socket assigns
live boolean false Enable LiveComponent mode
limit integer 5 Max visible messages
position string "top-center" Position: "top-left", "top-right", "top-center", "bottom-left", "bottom-right", "bottom-center"
auto_dismiss integer 5000 Auto-dismiss time in ms
show_close boolean true Show close button

Flash Attributes

Name Type Default Description
id string Flash message ID
position string "top-center" Position variant
type atom :info Message type: :info, :success, :warning, :error
preset boolean false Use compact preset toast styling
class string "" Additional CSS classes
show_close boolean true Show close button