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.