SaladUI.Toast (SaladUI v1.0.0)

Copy Markdown View Source

Toast notifications, parity target Sonner.

Unlike every other SaladUI component, toaster/1 does not manage a single open/closed state machine — it mounts once per page and manages an unbounded, dynamically created stack of independent toast cards entirely in JavaScript (assets/salad_ui/components/toast.js).

Mounting

<.toaster id="toaster" position="bottom-right" />

Triggering toasts

Use the helpers below from any handle_event/3:

def handle_event("save", _params, socket) do
  {:noreply, SaladUI.Toast.toast_success(socket, "Saved!")}
end

Rich-content toasts

Declare a :template next to the toaster and reference it by name:

<.toaster id="toaster">
  <:template name="new-follower">
    <img src={@avatar_url} class="size-8 rounded-full" />
    <p><%= @name %> followed you</p>
  </:template>
</.toaster>

SaladUI.Toast.toast(socket, template: "new-follower")

Customization

SaladUI ships no predefined per-variant palette — every toast renders in the card's neutral default (border-border bg-background text-foreground) unless you opt in, either toaster-wide or per call.

Toaster-wide, applied to every card of that variant:

<.toaster
  id="toaster"
  colors={%{success: "border-emerald-500/30 bg-emerald-50 text-emerald-900"}}
/>

Per-toast, overriding the toaster-wide default for just this call:

SaladUI.Toast.toast_success(socket, "Saved!",
  color: "border-emerald-500/30 bg-emerald-50 text-emerald-900"
)

color/colors replace the card's default border/background/text as one unit — the icon and title inherit the resulting text color, while the description keeps its own muted color. The toaster's class attr and a per-toast class opt instead append to the default classes, so reach for !-prefixed utilities there to reliably win the cascade:

SaladUI.Toast.toast(socket, "Something broke",
  class: "!border-fuchsia-500/30 !bg-fuchsia-50 !text-fuchsia-900"
)

unstyled: true drops every built-in class, leaving bare data-part elements for class to style from scratch:

SaladUI.Toast.toast(socket, "Bare toast", unstyled: true, class: "my-toast")

action/cancel accept an optional value map, merged into the event payload pushed back to the LiveView (alongside id):

SaladUI.Toast.toast(socket, "File deleted",
  action: %{label: "Undo", event: "undo_delete", value: %{file_id: file.id}},
  cancel: %{label: "Dismiss"}
)

def handle_event("undo_delete", %{"id" => _id, "file_id" => file_id}, socket) do
  # restore file_id
  {:noreply, socket}
end

Phoenix flash integration

Pass flash to render flash messages as toasts instead of a second notification system:

<.toaster id="toaster" flash={@flash} />

Summary

Functions

Drop-in-shaped alongside Phoenix.LiveView.put_flash/3 / Phoenix.Controller.put_flash/3 — same arity, same call sites, works on both.

Push a toast to a mounted <.toaster> (see toaster/1).

Removes one toast, or every toast on the given toaster if id is omitted.

Same as toast/3, with variant: "error".

Bridges @flash into toasts on the given toaster without patching the toaster's own DOM subtree (which would tear down its live stack/timers).

Same as toast/3, with variant: "info".

Same as toast/3, with variant: "success".

Transitions an existing toast (by id) to a new variant/content, patched in place.

Same as toast/3, with variant: "warning".

Mounts the toast stack. Render exactly once per page (typically the root layout).

Functions

put_toast(conn_or_socket, kind, message, opts \\ [])

Drop-in-shaped alongside Phoenix.LiveView.put_flash/3 / Phoenix.Controller.put_flash/3 — same arity, same call sites, works on both.

On a %Socket{} this behaves like toast/3 with variant: kind. On a %Plug.Conn{} (no toaster hook exists to push a command to — the page hasn't rendered yet) it falls back to Phoenix.Controller.put_flash/3, so a controller action followed by a redirect still shows something instead of silently dropping the message; once the LiveView the redirect lands on renders <.toaster flash={@flash} />, the flash bridge (toast_flash/1) picks it up and converts it to a real toast on connect.

toast(socket, message, opts \\ [])

Push a toast to a mounted <.toaster> (see toaster/1).

message is either a string, used as the toast's title, or (for template-based toasts, see toaster/1's :template slot) a keyword list/map of opts with no title:

socket = SaladUI.Toast.toast(socket, "Saved!")
socket = SaladUI.Toast.toast(socket, "Saved!", description: "Changes are live")
socket = SaladUI.Toast.toast(socket, template: "new-follower", variant: "default")

Call toast/4 directly to target a <.toaster> other than the default "toaster" id. See toaster/1's moduledoc for the full opts table.

toast(socket, toaster_id, message, opts)

toast_dismiss(socket, toaster_id \\ "toaster", id \\ nil)

Removes one toast, or every toast on the given toaster if id is omitted.

toast_error(socket, message, opts \\ [])

Same as toast/3, with variant: "error".

toast_flash(assigns)

Bridges @flash into toasts on the given toaster without patching the toaster's own DOM subtree (which would tear down its live stack/timers).

Usually rendered implicitly via toaster/1's :flash attribute; call it directly when the toaster and the LiveView holding @flash are mounted separately.

Attributes

  • id (:string) - Defaults to "toast-flash-bridge".
  • flash (:map) (required)
  • toaster (:string) - Defaults to "toaster".

toast_info(socket, message, opts \\ [])

Same as toast/3, with variant: "info".

toast_success(socket, message, opts \\ [])

Same as toast/3, with variant: "success".

toast_update(socket, id, opts)

Transitions an existing toast (by id) to a new variant/content, patched in place.

toast_warning(socket, message, opts \\ [])

Same as toast/3, with variant: "warning".

toaster(assigns)

Mounts the toast stack. Render exactly once per page (typically the root layout).

Server commands

  • "add" - Creates and mounts a new toast card, arms its timer.
  • "update" - Patches an existing card in place, keyed by id.
  • "dismiss" - Plays the exit animation and removes a card (or every card if id is omitted).

Component events

  • :on-dismiss - Fired whenever a toast is removed, with %{id:, reason:} in the payload.

Attributes

  • id (:string) - Defaults to "toaster".
  • position (:string) - Defaults to "bottom-right". Must be one of "top-left", "top-center", "top-right", "bottom-left", "bottom-center", or "bottom-right".
  • expand (:boolean) - Stack fully expanded instead of collapsed. Defaults to false.
  • visible-toasts (:integer) - Max stacked cards before overflow is collapsed. Defaults to 3.
  • gap (:integer) - px gap between stacked toasts when expanded. Defaults to 14.
  • swipe-direction (:string) - Defaults to nil.Must be one of "up", "down", "left", "right", or nil.
  • width (:integer) - Fixed card width in px. Defaults to 356.
  • offset (:any) - px gap from the viewport edge, or a %{top:, right:, bottom:, left:} map. Defaults to 24.
  • mobile-offset (:any) - Same as :offset, applied below the 600px breakpoint. Defaults to 16.
  • hotkey (:string) - Keyboard shortcut that moves focus into the front toast. Defaults to "Alt+T".
  • icons (:map) - toaster-wide icon overrides, e.g. %{success: "hero-sparkles"}. Defaults to %{}.
  • colors (:map) - per-variant toast color override, e.g. %{success: "border-emerald-500/30 bg-emerald-50 text-emerald-900"} — replaces the card's default border/background/text (icon + title inherit the text color; description keeps its own muted color) for that variant. No variant ships with a predefined color. Defaults to %{}.
  • class (:string) - extra classes merged onto every card — use !-prefixed utilities, e.g. "!bg-secondary", to reliably override a default color class. Defaults to nil.
  • flash (:map) - when given, also renders the flash-to-toast bridge (see toast_flash/1). Defaults to nil.
  • on-dismiss (:any) - Handler fired when any toast is removed. Payload includes id and reason. Defaults to nil.

Slots

  • template - named rich-content template, referenced from toast(socket, template: name). Accepts attributes:
    • name (:string) (required)