Logo

Show Django flash messages as toasts with Htmx

Learn how to display Django messages as user friendly toasts with Htmx

Published: 11 Oct, 2024

Updated: 11 Nov, 2024


Django has an amazing messages framework that you can use to notify users about various things that require their attention.

After setting up the framework, you simply them in templates using whatever layout you want. I usually rely on Bootstrap’s Alert component to display these messages. However, I decided that Toasts are more user friendly because they will subtly pop-up and dismiss themselves after some time.

In this post, I’ll show you how I implemented messages as toasts using Htmx in one of my projects.

Workflow

I will have a simple view that will render a template containing HTML representing the current messages available in the form of Bootstrap toasts. This is what a typically toast looks like:

{% if messages %}
{% for message in messages %}
<div
  class="toast align-items-center"
  role="alert"
  aria-live="assertive"
  aria-atomic="true"
>
  <div class="d-flex">
    <div class="toast-body">{{ message|safe }}</div>
    <button
      type="button"
      class="btn-close me-2 m-auto"
      data-bs-dismiss="toast"
      aria-label="Close"
    ></button>
  </div>
</div>
{% endfor %}
{% endif %}

I’ll iterate over the list of messages and output HTML for each toast with the messages content in the body.

Here’s the view:

from django.contrib import messages
from django_htmx.http import reswap, trigger_client_event

def toast_messages(request):
    storage = messages.get_messages(request)
    if len(storage) == 0:
        response = HttpResponse()
        return reswap(response, "none")

    response = render(request, "common/toast_messages.html")
    return trigger_client_event(response, "initToasts", after="swap")

If no messages are available, I will return an empty response and instruct htmx to not swap anything in the DOM. Otherwise, I will send rendered toasts template from before. I will also tell htmx to trigger an event called initToasts after content has been swapped in. We’ll use this even later to initialize the Bootstrap toasts.

I will place the htmx attributes directly on the toasts container:

<div hx-get="{% url "core:toast-messages" %}" hx-trigger="fetchToasts" hx-swap="afterbegin" class="toast-container top-0 end-0 p-3">
    {% include "common/toast_messages.html" %}
</div>

When the page loads, I want to render any messages available. Then, htmx will listen for the fetchToasts event and issues a GET request to the url specified. I want to trigger this event after every AJAX request. Here’s how:

document.addEventListener("htmx:afterRequest", (evt) => {
  if (!evt.detail.requestConfig.path.includes("toasts")) {
    document.querySelector("body").dispatchEvent(new Event("fetchToasts"));
  }
});

Use this function to initialize toasts:

function initToasts() {
  const toastEls = document.querySelectorAll(".toast");
  toastEls.forEach((el) => {
    const toast = window.bootstrap.Toast.getOrCreateInstance(el);
    toast.show();
    el.addEventListener("hidden.bs.toast", () => {
      el.remove();
    });
  });
}

This function needs to be called once when the DOM loads and also after the initToasts event is fired:

document.addEventListener("DOMContentLoaded", () => {
  console.log("ready");
  initToasts();
});

document.addEventListener("initToasts", () => {
  initToasts();
});

Customizing toasts

The toast component above is bare bones. I want to show different kinds of toasts depending on the message leveral (or severity). For example, I want the toast to have a red background for messages with level of error. I’m going to use custom template tags to implement those.

Dump the following in a file called toast_tags.py in one of your app’s templatetags directory.

from django import template

register = template.Library()


@register.simple_tag
def get_toast_color_scheme(toast_tag):
    if toast_tag == "info":
        return "text-bg-info"
    elif toast_tag == "success":
        return "text-bg-success"
    elif toast_tag == "warning":
        return "text-bg-warning"
    elif toast_tag == "danger":
        return "text-bg-danger"
    return ""


@register.simple_tag
def get_toast_close_button_color(toast_tag):
    if toast_tag == "info":
        return "btn-close-dark"
    elif toast_tag == "success":
        return "btn-close-white"
    elif toast_tag == "warning":
        return "btn-close-dark"
    elif toast_tag == "danger":
        return "btn-close-white"
    return ""

When the background color changes, I also need to change the color of the close button to make it visible.

Now, update your toast_messages.html file like so:

{% load toast_tags %}
{% if messages %}
{% for message in messages %}
<div
  class="toast align-items-center {% get_toast_color_scheme message.level_tag %}"
  role="alert"
  aria-live="assertive"
  aria-atomic="true"
>
  <div class="d-flex">
    <div class="toast-body">{{ message|safe }}</div>
    <button
      type="button"
      class="btn-close me-2 m-auto {% get_toast_close_button_color message.level_tag %}"
      data-bs-dismiss="toast"
      aria-label="Close"
    ></button>
  </div>
</div>
{% endfor %}
{% endif %}

Now the toast will be properly formatted according to the level of the message.

Conclusion

Django and Htmx make it simple to notify users of important messages via toasts. All you have to do is render the HTML, and then use Htmx to fetch any new messages after every AJAX request. Try it out in your project now.


Email me if you have any questions about this post.

Subscribe to the RSS feed to keep updated.