Toasts with Django+HTMX

This is a follow-up to my previous article, which showed how to implement a modal form with Django and HTMX. In this article, we’ll see how to add a user notification in the form of a toast.

Modal forms with Django+HTMX with toast

In the same spirit of my previous article, the solution I present here requires only a few lines of JavaScript and is reusable.

You can find the complete source code for this project on GitHub. I uploaded two versions: one using Bootstrap 4 and the other using Bootstrap 5. You’ll find each version in the corresponding branch. The code snippets in this article use Bootstrap 5.

This article is also available as a YouTube video

Step 1: create the toast element

First, we need the HTML element for the toast. It will be initially hidden and empty.

Place the following lines at the end of <body>:

<div class="position-fixed top-0 end-0 p-3">
  <div id="toast" class="toast align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
    <div class="d-flex">
      <div id="toast-body" class="toast-body"></div>
      <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
    </div>
  </div>
</div>

This code is directly taken from Bootstrap’s documentation, except that the toast body is empty.

Notice that I declared the ids toast and toast-body. We’ll use them from the JavaScript code.

Step 2: emit a second event from the view

In the previous article, we used the HX-Trigger header to raise a JavaScript event from the Django view:

return HttpResponse(status=204, headers={'HX-Trigger': 'movieListChanged'})

Now, we’ll change this to raise a second event. HTMX lets us do that with an alternative syntax for the HX-Trigger header. Instead of a simple string that names the event, we can provide a JSON string containing the names of the events associated with a payload. In our case, we need this JSON string to be:

{
  "movieListChanged": null,
  "showMessage": "<name of the movie> added."
}

As you can see, the movieListChanged is still there but doesn’t carry any data. I added the showMessage event with the associated message.

Here is the updated code for the view:

return HttpResponse(
    status=204,
    headers={
        'HX-Trigger': json.dumps({
            "movieListChanged": None,
            "showMessage": f"{movie.title} added."
        })
    })

Step 3: intercept the event

On the client-side, we must listen for this new showMessage event a display a toast when it occurs.

Here is the JavaScript code for that:

const toastElement = document.getElementById("toast")
const toastBody = document.getElementById("toast-body")

const toast = new bootstrap.Toast(toastElement, { delay: 2000 })

htmx.on("showMessage", (e) => {
  toastBody.innerText = e.detail.value
  toast.show()
})

As announced in step 1, we use the ids toast and toast-body to locate the toast elements in the DOM.

The third line creates the Toast object. This is specific to Bootstrap 5; you’ll have to adapt this line to your CSS framework.

In the event handler, we set the toast message from the event’s payload, and finally, show the toast.

In the next article, we’ll see how to do the same thing with Django’s messages framework.