DEV Community

Nikola Djordjevic
Nikola Djordjevic

Posted on

πŸš€ Auto-Clearing Flash Messages in Phoenix LiveView (with a Visual Countdown!)

LiveView gives us beautiful declarative ways to build interactive UIs without writing much JS. But one thing developers often want is this:

βœ… A flash message that shows up, waits a few seconds, fades away β€” and shows a countdown line while it's visible.

Here’s how to do it with almost no JS and a sprinkle of Tailwind + raw CSS.


πŸ”§ 1. Add the Flash Component

In your LiveView or component module (core_components.ex), define a flash like this if it doesn't exist already:

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="AutoClearFlash"
    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-sky-50 text-blue-500 ring-blue-500 fill-blue-900",
      @kind == :success && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
      @kind == :warn && "bg-amber-50 text-amber-900 shadow-md ring-amber-500 fill-amber-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 == :success} name="hero-check-circle-mini" class="h-4 w-4" />
      <.icon :if={@kind == :warn} name="hero-exclamation-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
Enter fullscreen mode Exit fullscreen mode

🧠 2. Auto-dismiss via Hook

Add a small JS hook (hooks.js):

let Hooks = {}
Hooks.AutoClearFlash = {
  mounted() {
    const ignoredIDs = ["client-error", "server-error"];
    if (ignoredIDs.includes(this.el.id)) return;

    setTimeout(() => this.el.click(), 2500);
  }
};

export default Hooks;
Enter fullscreen mode Exit fullscreen mode

Hook it up in your LiveSocket config:

import Hooks from "./hooks";
let liveSocket = new LiveSocket("/live", Socket, { hooks: { AutoClearFlash }, ... });
Enter fullscreen mode Exit fullscreen mode

🎨 3. Visual Countdown with Raw CSS

Inside your app.css, add this keyframes animation


@keyframes countdown {
  from { width: 100%; }
  to { width: 0%; }
}
Enter fullscreen mode Exit fullscreen mode

⏳ 4. Countdown bar

Expand your flash component with this countdown bar for beautiful visual effect. Add it just at the end of your flash component.

<div class="mt-2 h-1 w-full overflow-hidden rounded-full bg-white/30">
  <div class={[
    "h-full animate-[countdown_2.5s_linear_forwards]",
    @kind == :info && "bg-blue-500",
    @kind == :success && "bg-emerald-500",
    @kind == :warn && "bg-amber-500",
    @kind == :error && "bg-rose-500"
  ]} />
</div>
Enter fullscreen mode Exit fullscreen mode

βœ… That's It!

You now have:

  • A flash that disappears after 2.5s
  • A smooth fade-out animation
  • A subtle countdown bar like a fuse burning out

Let LiveView do the heavy lifting ✨


πŸ’¬ Got ideas for variations (e.g. pause on hover, different timers per flash type)? Let me know in the comments!

Top comments (0)