Buy Access to Course
16.

Toast Notifications

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

An important part of any functional beautiful site is a notification system. In Symfony, we often think of flash messages: messages that we render near the top of the page, for example, after the user submits a form. And yes, that is what I'm talking about. But just rendering them at the top of the page isn't good enough for us. Instead, I want to render them as rich, toast-style notifications in the upper right that disappear automatically with CSS transitions and can tie my shoes for me.

Rendering Flash Messages

On our CRUD controllers, I'm already setting a success flash message... but I'm not rendering it anywhere. In the templates/ directory, create a new _flashes.html.twig. To start, just loop over the success messages with for message in app.flashes('success')... and endfor:

6 lines | templates/_flashes.html.twig
{% for message in app.flashes('success') %}
// ... lines 2 - 4
{% endfor %}

Inside, I'll paste a very simple flash message, which will start fixed to the bottom of the page:

{% for message in app.flashes('success') %}
<div class="fixed bottom-0 right-0 m-4 p-4 bg-green-500 text-white rounded shadow">
{{ message }}
</div>
{% endfor %}

Next, in base.html.twig, instead of rendering the flashes somewhere near the top of the body, put them at the bottom. Say <div id="flash-container"> then {{ include('_flashes.html.twig') }}:

57 lines | templates/base.html.twig
<!DOCTYPE html>
<html>
// ... lines 3 - 15
<body class="bg-black text-white font-mono">
// ... lines 17 - 51
<div id="flash-container">
{{ include('_flashes.html.twig') }}
</div>
</body>
</html>

The id="flash-container" isn't important yet, but it will be useful later when we talk about Turbo streams.

So let's see if this works! Hit "Save" and... there we go! It's in a weird spot, but it shows up.

Making the Notification Pretty!

To make this look nicer, let's take a trip to Flowbite. Search for "toast". Ah, this has some great examples for different styles of toast notifications. This has me feeling dangerous!

Tip

I also recommend adding a data-turbo-temporary attribute to the root <div>. This will remove the flash message before Turbo takes its "snapshot" for caching, This means that if the user clicks "Back" to a page, the toast won't still be visible.

Back in _flashes.html.twig, I'll paste in some content:

{% for message in app.flashes('success') %}
<div
class="fixed top-5 right-5 flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
role="alert"
>
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
</svg>
<span class="sr-only">Check icon</span>
</div>
<div class="ms-3 text-sm font-normal">{{ message }}</div>
<button
type="button"
class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
aria-label="Close"
>
<span class="sr-only">Close</span>
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
</button>
</div>
{% endfor %}

This is heavily inspired by the Flowbite examples. But nothing really changed: we're still looping over the same collection and still dumping out the message. We've just got nice markup around this.

And I can't want to see it! I'll go to edit and "Save". Oh, that is wonderful! In the upper right where I want it and all done with CSS.

Making the Toast Closeable

Though, it doesn't auto close yet. Heck, it doesn't close at all! Since "closing" things will be a common problem, let's create a reusable Stimulus controller that can do that.

In assets/controller/, add a new closeable_controller.js. I'll cheat and grab the code from another controller... clear it out... then add a close() method. When this is called, it'll remove the entire element that the controller is attached to:

import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
close() {
this.element.remove();
}
}

To use this, in _flashes.html.twig, attach the controller to the top level element because that's what should be removed on close. Then, down on the button, say data-action="closeable#close":

27 lines | templates/_flashes.html.twig
{% for message in app.flashes('success') %}
<div
// ... lines 3 - 4
data-controller="closeable"
>
// ... lines 7 - 13
<button
// ... lines 15 - 17
data-action="closeable#close"
>
// ... lines 20 - 23
</button>
</div>
{% endfor %}

We don't need the click because this is a button, so Stimulus already knows that we want this to trigger on the click event.

Let's try it! Hit edit and Save. It's there... it's gone.

In just a few minutes of work, we created a beautiful and functional toast notification system! But, darn it, this is not cool enough for our 30 Days of LAST Stack mission! So tomorrow, we'll fancy-ify this with auto-close, CSS transitions and an animated timer bar.