Feedback
PhoenixPaper.Dialog, Progress, LoadingIndicator, Snackbar and Flash. MD3 has no alert banner or skeleton: reach for a snackbar or dialog, and a progress or loading indicator while content loads.
Dialog
MD3's dialog: 28dp corners on surface-container-high, a headline, supporting text and actions, with an optional hero icon. Always in the DOM and shown/hidden with the PhoenixPaper.Dialog.show/1 and hide/1 JS commands (like a generated core_components modal); focus is trapped, and Escape or a scrim click dismisses. variant="fullscreen" takes the whole screen; "responsive" is fullscreen on phones only.
basic with a hero icon, and fullscreen
| Option | Type | Default | Description |
|---|---|---|---|
close_label
|
string
|
"Close"
|
full-screen close button label |
icon
|
string
|
nil
|
a hero-* icon above the title (basic dialogs) |
id
|
string (required)
|
|
|
on_cancel
|
JS
|
%JS{}
|
|
paperize
|
boolean
|
true
|
|
show
|
boolean
|
false
|
shown immediately when this element first mounts |
variant
|
"basic" | "fullscreen" | "responsive"
|
"basic"
|
| Slot | Attrs | Description |
|---|---|---|
:actions
|
|
|
:inner_block
required |
|
|
:title
|
|
Show code
<.pp_button phx-click={PhoenixPaper.Dialog.show("confirm-delete")}>Delete</.pp_button>
<.pp_dialog id="confirm-delete" icon="hero-trash">
<:title>Delete this item?</:title>
This can't be undone.
<:actions>
<.pp_button variant="text" phx-click={PhoenixPaper.Dialog.hide("confirm-delete")}>Cancel</.pp_button>
<.pp_button variant="text" phx-click={JS.push("delete") |> PhoenixPaper.Dialog.hide("confirm-delete")}>
Delete
</.pp_button>
</:actions>
</.pp_dialog>
<%!-- Full screen on every screen, or only on phones --%>
<.pp_dialog id="new-event" variant="responsive">...</.pp_dialog>
Progress
MD3 progress indicators, linear or circular (an SVG arc), determinate with a value or indeterminate without one. M3 Expressive adds a wavy track, a thick (8dp) variant, and the stop indicator at the track's end.
Linear: determinate, indeterminate, wavy, thick
Circular
| Option | Type | Default | Description |
|---|---|---|---|
color
|
"primary" | "secondary" | "tertiary" | "error"
|
"primary"
|
|
label
|
string
|
nil
|
aria-label |
paperize
|
boolean
|
true
|
|
size
|
integer
|
40
|
circular only — diameter in pixels |
stop_indicator
|
boolean
|
true
|
determinate linear only |
thickness
|
4 | 8
|
4
|
|
value
|
integer
|
nil
|
0-100, nil for indeterminate |
variant
|
"linear" | "circular"
|
"linear"
|
|
wavy
|
boolean
|
false
|
M3 Expressive wavy active indicator |
Show code
<.pp_progress value={72} label="Uploading" />
<.pp_progress label="Loading" />
<.pp_progress value={60} wavy />
<.pp_progress value={45} thickness={8} />
<.pp_progress variant="circular" value={65} color="tertiary" />
<.pp_progress variant="circular" />
Loading Indicator
M3 Expressive's loading indicator: a shape that morphs through MD3's shape library as it spins, for waits too short for a progress bar (pull to refresh, a card loading). contained sets it on a primary-container circle for indicators over content. Animated in CSS; Safari shows a rotating soft burst unless the JS hook is on.
Plain, contained, colors
| Option | Type | Default | Description |
|---|---|---|---|
color
|
"primary" | "secondary" | "tertiary"
|
"primary"
|
|
contained
|
boolean
|
false
|
|
label
|
string
|
"Loading"
|
aria-label |
paperize
|
boolean
|
true
|
|
size
|
integer
|
48
|
px |
Show code
<.pp_loading_indicator label="Loading messages" />
<.pp_loading_indicator contained />
<.pp_loading_indicator color="tertiary" size={36} />
Snackbar
A brief message on MD3's inverse-surface, always that color, with an optional :action and a close button (on_close). two_line fits a longer message. Dismissal is yours (a Process.send_after/3 clearing the open assign, like generated flash messages), or client-side with on_close + auto_hide_duration. It's fixed at the bottom of the viewport (centered on phones, bottom-start from sm) and enters with MD3's fade-and-expand; positioned={false} drops the fixed placement so you can place it yourself, as these demos do. For Phoenix flash messages, use Flash below.
Try it
| Option | Type | Default | Description |
|---|---|---|---|
auto_hide_duration
|
integer
|
nil
|
milliseconds after which the snackbar triggers on_close itself (client-side; needs on_close set) |
on_close
|
JS
|
nil
|
when set, renders MD3's trailing close icon button, running this |
open
|
boolean
|
true
|
|
paperize
|
boolean
|
true
|
|
positioned
|
boolean
|
true
|
keep the viewport-anchored `fixed` positioning — set false to drop it and place the chip yourself (e.g. inside PhoenixPaper.Flash's stack) |
two_line
|
boolean
|
false
|
message above the action (MD3 longer action) |
| Slot | Attrs | Description |
|---|---|---|
:action
|
|
|
:inner_block
required |
|
Show code
<.pp_snackbar open={@message != nil}>
{@message}
<:action>
<.pp_button variant="text" phx-click="undo">Undo</.pp_button>
</:action>
</.pp_snackbar>
<%!-- on_close renders a trailing close button; pair it with auto_hide_duration
for a hook-free client-side auto-dismiss --%>
<.pp_snackbar on_close={JS.push("dismiss")} auto_hide_duration={5000}>
Link copied
</.pp_snackbar>
<%!-- two_line: a longer message above its action --%>
<.pp_snackbar two_line>
Your draft was saved on this device. Sign in to sync it everywhere.
<:action><.pp_button variant="text" phx-click="sign_in">Sign in</.pp_button></:action>
</.pp_snackbar>
Flash
Phoenix's @flash rendered as stacked snackbars: the Material counterpart of a generated core_components' flash_group. Drop pp_flash_group once in the root layout. Dismiss is wired to LiveView's built-in lv:clear-flash (no handler in your LiveView); auto_hide_duration is opt-in. Monochrome and text-only by spec: every kind looks the same, an inverse-surface snackbar.
Trigger a real flash
This whole page renders a pp_flash_group bound to @flash instead of the default flash_group. The buttons above call put_flash/3; a real chip slides in at the top-right, auto-hides after 6s, or dismiss it with the ✕ (LiveView's built-in lv:clear-flash, no handler). It also sets connection_notices: stop the server and a "We can't find the internet" chip appears until the socket reconnects.
Appearance (inline, static)
The same chips shown inline (each is a pp_snackbar positioned=false) rather than fixed to the viewport corner:
| Option | Type | Default | Description |
|---|---|---|---|
pp_flash_group auto_hide_duration
|
integer
|
nil
|
milliseconds after which each chip clears itself via lv:clear-flash (opt-in) |
pp_flash_group client_error_title
|
string
|
"We can't find the internet"
|
|
pp_flash_group connection_notices
|
boolean
|
false
|
also render the hidden client/server connection-lost chips (see the module doc) |
pp_flash_group flash
|
map (required)
|
|
the @flash map from the assigns |
pp_flash_group kinds
|
list
|
[:info, :error]
|
flash keys to render, in stacking order |
pp_flash_group paperize
|
boolean
|
true
|
|
pp_flash_group reconnecting_text
|
string
|
"Attempting to reconnect"
|
|
pp_flash_group server_error_title
|
string
|
"Something went wrong!"
|
|
pp_flash auto_hide_duration
|
integer
|
nil
|
|
pp_flash flash
|
map (required)
|
|
|
pp_flash kind
|
atom (required)
|
|
|
pp_flash paperize
|
boolean
|
true
|
Show code
<%!-- Drop pp_flash_group once, where a generated <.flash_group> goes.
auto_hide_duration is opt-in; dismissal needs no handler (lv:clear-flash).
connection_notices adds the generated flash_group's connection-lost chips. --%>
<.pp_flash_group
flash={@flash}
auto_hide_duration={6000}
connection_notices
client_error_title={gettext("We can't find the internet")}
/>
# In the LiveView: an ordinary put_flash/3 shows a chip
def handle_event("flash_info", _params, socket),
do: {:noreply, put_flash(socket, :info, "Workbook saved.")}
def handle_event("flash_error", _params, socket),
do: {:noreply, put_flash(socket, :error, "Could not reach the guest agent.")}