20 feedback components — alerts, banners, loading states, progress, skeletons, notifications, toasts, error displays, and confirmation patterns.

Module: PhiaUi.Components.Feedback

import PhiaUi.Components.Feedback

Table of Contents

Alerts & Banners

Loading

Progress

Status

Notifications & Toasts

Error & Empty States

Confirmation


alert

Non-interactive feedback banner with variants and optional icon slot.

Variants: default · destructive · warning · success

<.alert variant="success">
  <:icon><.icon name="check-circle" /></:icon>
  Your changes have been saved.
</.alert>

<.alert variant="destructive">
  <:icon><.icon name="x-circle" /></:icon>
  Failed to process payment. Please try again.
</.alert>

<.alert variant="warning">
  <:icon><.icon name="alert-triangle" /></:icon>
  Your trial expires in 3 days.
  <:action><.button size="sm" variant="outline">Upgrade</.button></:action>
</.alert>

<%!-- Dismissable --%>
<.alert variant="default" phx-click="dismiss_alert" id="info-alert">
  New features are available in this version.
</.alert>

Top-of-page notification bar with optional action and dismiss button.

<.banner variant="info" phx-click="dismiss_banner">
  System maintenance scheduled for Sunday 2am–4am UTC.
  <:action><.button size="sm" variant="ghost">Learn more</.button></:action>
</.banner>

announcement_bar

Marquee-scrolling announcement banner.

<.announcement_bar items={["Free shipping on orders over $50", "New products added weekly", "Use code SAVE20 for 20% off"]} />

GDPR-style cookie consent banner with accept/decline.

<.cookie_consent
  on_accept="accept_cookies"
  on_decline="decline_cookies"
  policy_url="/privacy"
/>

global_message

App-level flash-style message bar. Hook: PhiaGlobalMessage.

<%!-- In your root layout --%>
<.global_message id="app-flash" />
# Trigger from LiveView
def handle_info({:show_message, msg}, socket) do
  {:noreply, push_event(socket, "phia-global-message", %{message: msg, type: "success"})}
end

spinner

Inline loading spinner.

<.spinner />
<.spinner size="lg" class="text-primary" />

<%!-- Inside a button --%>
<.button disabled={@loading}>
  <%= if @loading do %>
    <.spinner size="sm" class="mr-2" /> Loading…
  <% else %>
    Save
  <% end %>
</.button>

Sizes: :sm · :md (default) · :lg


loading_overlay

Full-container overlay with spinner and optional message.

<div class="relative">
  <.loading_overlay visible={@loading} message="Loading data…" />
  <.table rows={@rows}>…</.table>
</div>

loading_dots

Three animated dots — use for chat "typing" indicators.

<.loading_dots />
<.loading_dots size="lg" class="text-primary" />

loading_bar

Indeterminate top-of-page loading bar (like YouTube/GitHub).

<.loading_bar visible={@page_loading} />

skeleton

Content placeholder while data loads.

<%!-- Inline --%>
<.skeleton class="h-4 w-32 rounded" />
<.skeleton class="h-10 w-full rounded-lg" />

<%!-- Pre-built variants --%>
<.skeleton_list items={5} />    <%!-- 5 row skeletons --%>
<.skeleton_form fields={4} />   <%!-- 4 input field skeletons --%>
<.skeleton_table_row cols={4} />
<.skeleton_profile />           <%!-- Avatar + text lines --%>

progress

Horizontal progress bar.

<.progress value={65} />
<.progress value={@percent} class="h-2 [&>div]:bg-green-500" />

Attrs: value (0–100), class


circular_progress

SVG circular progress ring.

<.circular_progress value={75} size={80} stroke_width={6} />
<.circular_progress value={@cpu_usage} label="CPU" />

labeled_progress

Progress bar with label and percentage text.

<.labeled_progress label="Storage" value={68} unit="GB used of 100GB" />

segmented_progress

Multi-section progress bar for multi-step flows.

<.segmented_progress steps={4} current_step={2} />

quota_bar

Stacked bar showing quota usage with colour zones.

<.quota_bar used={8.2} total={10} unit="GB" warn_at={80} danger_at={90} />

step_progress_bar

Horizontal bar with step dots and labels.

<.step_progress_bar current={2} steps={["Details", "Address", "Payment", "Review"]} />

status_indicator

Dot indicator with colour-coded status.

<div class="flex items-center gap-2">
  <.status_indicator status={:online} />
  <span>Alice Smith</span>
</div>

Statuses: :online · :offline · :away · :busy · :error


connection_status

Shows LiveView socket connection state.

<.connection_status />

live_indicator

Pulsing "LIVE" badge.

<.live_indicator />
<.live_indicator label="Broadcasting" />

toast

Programmatic toast notification. Fire with put_flash/3 or push_event/3.

<%!-- In root layout --%>
<.toast flash={@flash} />
# In a LiveView event handler
def handle_event("save", _params, socket) do
  case save(socket.assigns.form) do
    {:ok, _} ->
      {:noreply, put_flash(socket, :info, "Saved successfully!")}
    {:error, _} ->
      {:noreply, put_flash(socket, :error, "Failed to save.")}
  end
end

snackbar

Bottom-anchored notification with action button.

<.snackbar id="undo-snack" message="Item deleted" action_label="Undo" on_action="undo_delete" />

sonner

Toast stack manager inspired by Sonner. Supports queueing multiple toasts. Hook: PhiaSonner.

<%!-- In root layout --%>
<.sonner id="app-toasts" position={:bottom_right} />
# Trigger from LiveView
push_event(socket, "phia-sonner", %{
  type: "success",
  title: "Saved",
  description: "Your changes have been saved.",
  duration: 4000
})

notification

Notification item in a notification list or notification_center.

<.notification_center id="notif-center">
  <%= for n <- @notifications do %>
    <.notification_item
      title={n.title}
      body={n.body}
      timestamp={n.inserted_at}
      read={n.read_at != nil}
      icon={n.icon}
      phx-click="mark_read"
      phx-value-id={n.id}
    />
  <% end %>
</.notification_center>

empty_state

Centred placeholder for empty lists.

<.empty_state
  icon="inbox"
  title="No messages"
  description="When you receive messages, they'll appear here."
>
  <:action>
    <.button phx-click="compose">Compose message</.button>
  </:action>
</.empty_state>

result_state

Success / error / warning full-page or section result screen.

<.result_state
  status={:success}
  title="Payment confirmed"
  description="Your order #1234 has been placed."
>
  <:action>
    <.button href="/orders">View orders</.button>
  </:action>
</.result_state>

Statuses: :success · :error · :warning · :info


error_display

Formatted error message with stack trace (development mode).

<.error_display error={@error} show_trace={Mix.env() == :dev} />

popconfirm

Inline confirm/cancel popover for destructive actions.

<.popconfirm
  id="delete-confirm"
  message="Are you sure you want to delete this record? This cannot be undone."
  on_confirm="delete_record"
  confirm_label="Yes, delete"
>
  <.button variant="destructive">Delete</.button>
</.popconfirm>

step_tracker

Multi-step wizard tracker with icons and statuses.

<.step_tracker current={@step}>
  <:step status={:complete} icon="check">Account</:step>
  <:step status={:active} icon="user">Profile</:step>
  <:step status={:pending}>Plan</:step>
  <:step status={:pending}>Confirm</:step>
</.step_tracker>