Flash messages with Toast.js and Turbo

I don’t need to emphasize the huge use that notifications have in modern applications, we can just think of everyday situations. You go into a website that has a contact form, fill in your email, subject, message, etc, then click ‘Send’ and… nothing happens. Was it sent correctly? Is the page even doing anything? Do I fill it in and send it again, just in case?

The point is that the user has to be informed, in almost every moment, of what’s going on in the app or website. And flash messages come in very handy for this.

In this article, we’re going to tackle the setup and styling of flash messages like these ones:

Stack

To set up reusable flash messages, we’re going to assume you’re in a > Rails 7 project, use toastify-js library to define the messages, and then Turbo to render them. Turbo comes with 8 actions that help to get a looot of things done, but for this, we’re going to use a custom one. We’ll see how to do that in the following section.

Toastify setup

To define the custom turbo stream action that will add the notification to the DOM, we’ll extend the Turbo’s StreamActions module, so it includes our own javascript function. We’ll call our custom action toast:, and we’ll use toastify inside it to customize and render our toast message:

window.Turbo.StreamActions.toast = function() {
 const text = this.getAttribute("text")
 const className = `toast-${this.getAttribute("type")}`
 const duration = Number(this.getAttribute("duration"))
 const gravity = this.getAttribute("gravity")
 const position = this.getAttribute("position")

 Toastify({
    text,
    className,
    duration,
    gravity,
    position,
    stopOnFocus: true,
 }).showToast()
}

Last but not least, don’t forget to import toastify! Here’s the full file, which we’ll place in app/javascript/turbo_streams/toast.js

// app/javascript/turbo_streams/toast.js
import Toastify from "toastify-js" 

window.Turbo.StreamActions.toast = function() {
 const text = this.getAttribute("text")
 const className = `toast-${this.getAttribute("type")}`
 const duration = Number(this.getAttribute("duration"))
 const gravity = this.getAttribute("gravity")
 const position = this.getAttribute("position")

 Toastify({
    text,
    className,
    duration,
    gravity,
    position,
    stopOnFocus: true,
 }).showToast()
}

Rails 7 comes with importmap, so we’ll also have to pin Toastify in config/importmap.rb:

pin "toastify-js", to: "https://ga.jspm.io/npm:toastify-js@1.12.0/src/toastify.js"

That is the specific version that we use in this example, but you can also run

bin/importmap pin toastify-js

And then add toastify’s styles in application.html.erb

<%= stylesheet_link_tag "https://ga.jspm.io/npm:toastify-js@1.12.0/src/toastify.css", "data-turbo-track": "reload" %>

Toastify usage

Now that we have everything we need, we’ll define a helper so that we can easily invoke flash messages from wherever we want in our app. This helper will create a turbo-stream tag with action toast in the DOM, which will execute the javascript function we created before.

<turbo-stream action="toast"></turbo-stream>

Many of the method’s attributes are pre-defined, but we can set up the flash message type (alert, notice, success, etc), the duration, the position in the screen and whether we want it to close after some time or not:

module ToastHelper
 def toast(text, type: 'notice', duration: 3000, gravity: 'top', position: 'right', close: true)
  turbo_stream_action_tag(:toast, type:, text:, duration:, gravity:, position:, close:)
 end
end

Lastly and in the same file, we need to register toast in Turbo’s actions:

Turbo::Streams::TagBuilder.prepend(TurboStreams::ToastHelper)

Now, all we need to do is call that method! Like this, for example:

<%= turbo_stream.toast('Here goes your (by default) notice message') %>

Or, if we go with the typical approach of parameterizing the message in the redirect or over the flash object:

if @element.update(element_params)
 redirect to elements_path, notice: 'Element updated successfully'
end

Then, in each view, we can have something like:

<% flash.each do |type, text| %>
  <%= toast(text, type:) %>
<% end %>

Keep in mind that the setup is something that you do just once, and then all you do is invoke the toast method.

Toastify styles

What happened to me was that toastify’s styles were not great. I couldn’t find a way to change them without modifying a lot of the simple javascript controller that I had, so I ended up setting the flash message’s styles in a style file, toast.css:

.toast-notice, .toast-alert {
 color: white;
 width: auto;
 position: fixed;
 top: 2rem;
 right: 2rem;
 padding: .8rem;
 border-radius: .5rem;
 z-index: 100;
}
.toast-alert {
 background-color: #c44a54;
}
.toast-notice {
 background-color: #24a860;
}

Which result in flash messages like this: