Toast notifications using a JavaScript custom element

posted by Ayush Newatia
8 January, 2021



Toast notifications are a great way to give the user ephemeral, unobtrusive feedback about an action they have just carried out.

Twitter uses them in response to a lot of actions such as posting a new tweet or deleting a tweet. HEY also uses them to confirm email categorisation actions among other things.

Confirmation of a new tweet
HEY confirming that an email was moved as requested

A plethora of applications (including my app, Chapter24!) use Toasts in a variety of ways and these are just a couple of examples I could recall off the top of my head. In this post I’ll explain how to create a Toast with JavaScript using custom elements.

I’ll also be using the CSS Attribute Module technique to style the toasts so it’s worth understanding that concept if you’re not already familiar with it!

Anyway, let’s dig in!

Technical approach

We’re going to design this Toast component to appear when a toast-notification element is added to the DOM and hide automatically after 5 seconds. We’ll also add an attribute to make the Toast manually dismissible, in which case it won’t disappear automatically.

Using this approach means we don’t need to write additional JavaScript to show or hide Toasts; meaning we can show toasts from the server using a variety of techniques such as AJAX or the most recent “hot” thing: Turbo Streams.

Implementing the Toast custom element

Using a JavaScript custom element means that all our logic will be encapsulated within a single class and we’ll get a handy callback for when the element is added to the DOM.

// toast.js

export class Toast extends HTMLElement {

  // This is called whenever a Toast element connects to the DOM
  connectedCallback() {
    this.show()
  }

  show() {
    this.isVisible = true

    // If it's dismissible then we add a click listener to hide the toast
    if (this.isDismissible) {
      this.addEventListener("click", () => {
        this.hide(0)
      });

    // Otherwise we just hide it after 5 seconds
    } else {
      this.hide(5000)
    }
  }

  hide(delay) {
    setTimeout(() => {
      this.isVisible = false
    }, delay)

    // 1 second after hiding the toast, we remove it from the DOM
    setTimeout(() => {
      this.remove();
    }, delay + 1000)
  }

  get isVisible() {
    return this.getAttribute("am-visible") || false
  }

  set isVisible(visible) {
    this.setAttribute("am-visible", visible)
  }

  get isDismissible() {
    return this.getAttribute("am-dismissible") != null
  }
}

We’ll also need to register this class as a custom element.

// index.js

import { Toast } from "./toast"

customElements.define('toast-notification', Toast)

Some CSS is required to position the Toast in a fixed location near the top of the web page; implement the hiding and showing functionality; and to add an x for a dismissible Toast.

/* toast.css */

/* Base component styling */
toast-notification {
  opacity: 0;
  text-align: center;
  border-radius: 8px;
  padding: 4px 8px;
  position: fixed;
  z-index: 999; /* Make sure the it's on top of everything */
  top: 24px;
  transition: opacity 0.25s; /* Fade in and out */
  left: 50%;
  transform: translateX(-50%); /* Horizontally center it on the page */
  height: auto;
  background: blue;
  color: white;
}

/* Set opacity when set as visible in the attribute module */
toast-notification[am-visible~=true] {
  opacity: 1;
}

/* Add space for the 'x' to dismiss a dismissible Toast */
toast-notification[am-dismissible] {
  padding-right: 32px;
  pointer-events: none; /* Disable pointer events on the Toast; we only want the 'x' to be clickable */
}

/* Configure the 'x' to dismiss the Toast */
toast-notification[am-dismissible]:after {
  position: absolute;
  content: '✕';
  text-align: center;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  pointer-events: all; /* Allow pointer events only on the 'x' */
}

And that’s it!

Now when we add a toast-notification DOM element to the page, it’ll automatically appear at the top of 5 seconds!

Demo

As they say, the proof is in the pudding. So I’ve set up a demo on CodePen that you can check out and have a play with!

Here’s a GIF of the Toasts in action when added to a toasts-container element from a browser console.

It's as easy as setting innerHTML!

(NB: toasts-container is not a custom element; it’s just a semantic HTML tag. Check out this amazing blog post from Jared White for more info!)

Conclusion

Personally, I love toasts as a web component. It’s such a great way to confirm to a user that an action has indeed taken place.