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)