Recently, I saw a discussion on Bluesky about making flash messages in Phoenix LiveView disappear automatically:
Elixir Forum post "How to make flash message disappear after 5 seconds" created in 2020, with the latest reply in April 2025, remains unresolved.
It turns out this is something I already solved a while ago. Let me walk you through how I implemented automatic flash dismissal in my own project.
Step 1: create a custom hook
First, I created a new file at assets/js/hooks/auto-dismiss-flash.js
(note: I prefer keeping one file per hook for clarity) with the following code:
export default {
mounted() {
setTimeout(() => {
this.el.style.transition = "opacity 0.5s";
this.el.style.opacity = "0";
setTimeout(() => this.el.remove(), 500);
}, 5000); // Wait 5 seconds before starting to fade out
},
};
This hook:
- Waits 5 seconds,
- Fades out the flash message over 0.5 seconds,
- Then removes it from the DOM.
Step 2: register the Hook in app.js
Next, I updated assets/js/app.js
to load and register the new hook:
import AutoDismissFlash from "./hooks/auto-dismiss-flash";
const Hooks = {
AutoDismissFlash,
};
const liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
longPollFallbackMs: 2500,
params: { _csrf_token: csrfToken },
});
Now the hook is available for use anywhere in my LiveView templates.
Step 3: attach the hook to the flash Component
Finally, I updated my flash
component in lib/myapp_web/components/core_components.ex
to use the AutoDismissFlash
hook:
@doc """
Renders flash notices.
## Examples
<.flash kind={:info} flash={@flash} />
<.flash kind={:error} flash={@flash} />
"""
attr :id, :string, doc: "Optional id for the flash container"
attr :flash, :map, default: %{}, doc: "The map of flash messages to display"
attr :title, :string, default: nil
attr :kind, :atom, values: [:info, :error], doc: "Used for styling and flash lookup"
attr :rest, :global, doc: "Additional HTML attributes for the flash container"
slot :inner_block, doc: "Optional custom content for the flash message"
def flash(assigns) do
assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end)
~H"""
<div
:if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
id={@id}
phx-hook="AutoDismissFlash"
phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
role="alert"
class={[
"fixed top-2 right-2 mr-2 w-80 sm:w-96 z-50 rounded-lg p-3 ring-1",
@kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
@kind == :error && "bg-rose-50 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
]}
{@rest}
>
<p :if={@title} class="flex items-center gap-1.5 text-sm font-semibold leading-6">
<.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
<.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
<%= @title %>
</p>
<p class="mt-2 text-sm leading-5"><%= msg %></p>
<button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
<.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
</button>
</div>
"""
end
By adding phx-hook="AutoDismissFlash"
to the container, each flash message now automatically fades out and disappears without any further user interaction.
Sources
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.