💡

Cleaner flash notifications with AlpineJS

AlpineJS Ruby on Rails UI

If you scaffold your views in Rails, you know that flash messages insert themselves straight into your pages after submission. That's not very elegant and gets messy if you have multiple forms. Let's make a flash message worthy of its name.

There's one thing I immediately remove in any Rails project I start : this ugly flash message section that shows up when you submit a form.

green.png The default rigid flash messages in Rails

Here's what we will implement instead : a clean auto-fading centered flash that subtly gives user feedback above the rest of your content. It'll work with Rails redirects so you won't really have to change anything in your controllers.

flash.gif A clean flash that works with Rails redirects

1. Add AlpineJS

For components not needing Stimulus, AlpineJS is the simplest way to inject sprinkles of JS into your Rails views. For small projects and testing, you can install Alpine using the recommended CDN :

<head>
  <script src="//unpkg.com/alpinejs" defer></script>
</head>

For a more durable installation, follow these 2 steps :

  1. In your project directory, in your terminal, type:

    # This should add a new line in importmap.rb
    
    bin/importmap pin alpinejs
    
  2. In application.js, add:

    import Alpine from "alpinejs";
    window.Alpine = Alpine;
      document.addEventListener("turbo:load", function (event) {
      window.Alpine.start();
    });
    

2. Remove the old flash layout

In the index and show pages of each resource that presents a notification, remove this :

<% if notice.present? %>
  <p id="notice"><%= notice %></p>
<% end %>

This is optional but you can also remove the markup that displays the validation errors in a similar way :

<% if post.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
    <ul>
      <% post.errors.each do |error| %>
        <li><%= error.full_message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

... and then display the validations errors directly in the flash, by modifying your controller :

# add this in your Posts controller, in the statement where @post doesn't save 

flash[:error] = @post.errors.full_messages.join(', ')

3. Implement the new flash

In application.html.erb, in your <body> tag, somewhere before the main content yield, add this :

<body>
  <main class="container flex px-5 mx-auto mt-28">
    <%= render 'shared/flash' %>
    <%= yield %>
  </main>
</body>

Now create your flash partial in app/views/shared/_flash.html.erb

<div id="flash" class="flex justify-center">
  <div class="fixed z-50 flex flex-col mt-6 w-fit">
    <% flash.each do |type, message| %>
      <div x-init="() => { flashShown = true; setTimeout(() => { flashShown = false }, 4000) };"
        x-data="{ flashShown: false }"
        x-show="flashShown"
        x-cloak
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0"
        x-transition:enter-end="opacity-100"
        x-transition:leave="transition ease-out duration-200"
        x-transition:leave-start="opacity-100"
        x-transition:leave-end="opacity-0">
        <div class="text-sm font-medium">
          <%= message %>
        </div>
      </div>
    <% end %>
  </div>
</div>

And voilà, you know have a clean auto-fading centered flash that subtly gives user feedback above the rest of your content.

With this foundation, it is also possible to implement flashes without redirects, using the Streams feature from Turbo, as explained in this great article.

→ Let's get in touch

Got questions or feedback about this article? Interested in discussing your project? I'm all ears and always open to new opportunities. Shoot me an email and tell me what you have in mind.