CascadiaJS 2021: Building live experiences with Web Components

Today is the first day of CascadiaJS 2021!

crowd applauding

To celebrate, this post will be the first in a series talking about how we built out some of live experiences you will see on the web during the hybrid conference.


This year, we had two areas of our live stage that we wanted to update. Our Q&A experience and mirroring the chat in our Discord server (seen here outlined in red). The live conference view for CJS 2021


The first was our Q&A section. This allowed attendees to submit questions for the speakers, and then vote on whichever ones they really wanted answers to. Last year, this was accomplished with a small React app that was dropped into the stage via an iframe. This worked great, but posed some limitations.

  1. Since it was an iframe, it was difficult to interact with other aspects of the stage. We wanted to wipe the questions each time a new talk started and be aware which users submitted and voted for questions so that users could only vote for a specific question once. This was accomplished with query params passed to the iframe, but overall felt a bit hacky and was prone to error.
  2. Because we were just using an iframe, we couldn't give any meaningful control around things like styling to the rest of our colleagues building the stage. This meant that if anything needed to change, we would have to open a pr against another project and release that before being able to test it. Not the worst thing, but the back and forth it caused when it came to development just wasn't as efficient as we would have liked.

Discord View

Last year, we hosted our live chat for the conference on Slack. This year we decided to shake things up and use Discord as our digital chat home for the conference. We still wanted to mirror the chat in the livestream channel, but because it's an entirely different platform, we would need to build out functionality similar to last year's. Additionally, we wanted to handle media and text a bit better. Discord allows you to embed gifs and auto-unrolls img links dropped in a chat. Discord can also handle text formatted in Markdown, so we wanted to reflect that in the mirror transcript as well. Like the Q&A section, we also wanted to supply a great base experience, but allow it to be overridden or extended if needed.

Open Source

CascadiaJS is a big believer in building in public and helping to share what we learn along the way. Any solution we built, we wanted it usable by the greater community. For this reason, we also decided to build the solutions as individual Web Components, so they could be used by any JavaScript project, regardless of framework (or lack thereof).

How we did it

In the end, we published our Web Components in two separate packages: @cascadiajs/discord-mirror and @cascadiajs/q-and-a. Later posts will go more in depth on how we solved specific problems, but here are the tools we ended up using.


For integrating with Discord and syncing messages we used Abbot. Abbot helps you to build chatbots that work in Discord, Slack, or MS Teams. They take a lot of the pain out of building and configuring your integration with various chat platforms and let you interact with the different chat platforms in a nice, standardized way. They just launched a new feature, Patterns, that let you subscribe to messages within a specific channel that match a pattern. This worked great for our use case as it allowed us to subscribe to the #livestream channel people were chatting in while the talks happened. Read more about how Patterns work here.


Both of the Web Components themselves were built with Stencil. Stencil is great because it lets you work with many tools/concepts you may already be familiar with using in other apps, like JSX, Typescript, and React-style data-binding, while giving you powerful tooling around things like, project and component generations, state management, and working with the Shadow DOM. Since our components were using the Shadow DOM, we were able to provide a base set of styles, but allow users to override them via Shadow DOM Parts. (More on this in a future post).


Abbot helped us to get messages out of Discord, Stencil helped us to visualize those messages and allow users to ask questions to the speaker, but how do we go about connect them together? That's where Firebase comes in. With Firebase, we were able to write simple functions to accept the data we needed and load it into a Realtime Database. The best benefit to this was, thanks to the Firebase SDK, we could subscribe to any changes that would happen in the database within our components and have them reflected in the UI without needing to do repetitive polling.

What next?

Like I said earlier, this is the first post in a series I'll be publishing over the next weeks talking about different problems we faced and other learnings to take away from building these components. Some topics will include handling mixed media within messages, allowing customization of the components, providing user choice between different backend solutions, and others. If there's anything specific you want me to cover, reach out and let me know! Meanwhile, if you'd like to see the code for our Web Components, they can be found here:

Until then, ENJOY THE CONFERENCE! This really has been a labor of love for the organizer team, and we can't wait to see you all in person and online. If you see me hanging around in Discord or Gather, say hi!