Evolution of a chat widget

By Alex Reichert


Papercups is an open source alternative to customer messaging tools like Intercom and Zendesk.

GitHub repo: https://github.com/papercups-io/papercups


When we started building Papercups, we wanted to build a product that developers and designers would love. To us, one of the things this means is as much control over the design and behavior of the live chat interface on your website as possible, with reliable defaults to fall back on.

Over the past 8 months, we've launched several iterations of our embeddable chat component. This is the story of the three major releases we've pushed so far 🚀

The initial demo (v0)

We built the first version in ~3 weeks. For the design, we wanted something relatively unopinionated, and took some inspiration from some of the major players out there (e.g. Intercom, Drift, Crisp).

After looking at some of the "competition", another thing we knew we wanted was the ability to embed the chat UI as a React (+ Vue/Svelte/etc) component in our apps. We would certain support the standard approach of pasting in a script tag in the HTML as well, but we liked the granularity of control that a importable React component gave us.

Demo v1

It didn't take long to build an MVP. It turns out that the frontend implementation of a basic chat feature is pretty straightforward — all you really need is a way to send and display messages. So we started off with a simple React component that did just that, and started looking for developers to try us out.

The first hiccup we had was managing CSS clashes with the websites that were using us. At first I thought it was "clever" (the classic excuse of a lazy developer) that our chat UI would inherit the typography of the website, but this didn't always turn out nicely. Another issue was dealing with !important properties in the parent CSS, which was annoyingly common.

To fix these issues, we knew we either had to use the shadow DOM or embed most of the chat UI in an iframe. We ended up going with the latter, mostly because it was something we were more familiar with.

The first version had pretty limited customizability, but it was enough to get started. You could customize the header content, the color theme, and the greeting message. In practice, the code looked roughly like this:

import React from 'react';
import {ChatWidget} from '@papercups-io/chat-widget';

const App = (props) => {
  return (
    <ChatWidget
      title="Welcome to Papercups!"
      subtitle="Ask us anything in the chat window below 😊"
      greeting="Hi there! Have any questions or feedback?"
      primaryColor="#1890ff"
      {...props}
    />
  );
};

The nice thing about using React props for these fields was that it made it easy to set up a demo page where you could customize things in real-time. You can still play around with it here 👉 app.papercups.io/demo

After the launch (v1)

After launching on Hacker News, we started to learn a lot more about what companies wanted from their live chat products.

Here are a few of the things people asked for:

  • Integrations with bot providers (e.g. Google Dialogflow, Amazon Lex, Rasa)
  • Support for React Native/Flutter/Vue components
  • The ability to automatically collect a user's name and email upfront
  • A variety of options for dealing with the situation where no one is available to respond to incoming messages (e.g. showing agent availability in the chat, displaying an away message outside of business hours, or just completely hiding the chat component under certain circumstances)

Since then, we've built out a basic React Native library, set up webhook events and a public API (which is all you really need to be able to get started with most chat bot providers 😬), and added a ton of new configuration options to our chat component:

import React from 'react';
import {ChatWidget} from '@papercups-io/chat-widget';

const App = (props) => {
  return (
    <ChatWidget
      title="Welcome to Papercups!"
      subtitle="Ask us anything in the chat window 😊"
      primaryColor="#1890ff"
      greeting="Hi there! How can I help you?"
      awayMessage="Sorry, we're not available at the moment!"
      agentAvailableText="Agents are online!"
      agentUnavailableText="Agents are not available at the moment."
      customer={{
        name: 'Test User',
        email: 'test@test.com',
        // Ad hoc metadata
        metadata: {
          plan: 'starter',
          registered_at: '2020-09-01',
          age: 25,
          valid: true,
        },
      }}
      requireEmailUpfront
      showAgentAvailability
      hideOutsideWorkingHours
      popUpInitialMessage={1000}
      isOpenByDefault
      persistOpenState
      iconVariant="filled"
      position={{side: 'left', offset: 80}}
      onChatLoaded={() => console.log('Chat loaded!')}
      onChatClosed={() => console.log('Chat closed!')}
      onChatOpened={() => console.log('Chat opened!')}
      onMessageReceived={(message) => console.log('Message received!', message)}
      onMessageSent={(message) => console.log('Message sent!', message)}
      {...props}
    />
  );
};

We've also been super lucky to get some help from the open source community on building out our Flutter library, as well as a basic Vue wrapper for our chat component. ❤️

One other thing we did was make it possible to set all the chat configuration defaults in the dashboard, so you don't have to always change it in the code if you don't want to. This makes it a bit easier for non-technical users, and prevents the need to redeploy your app whenever you want to change your chat configuration details.

Getting started

Complete UI customizability (with ReactJS)

Even though we've added a bunch of new ways to configure our embeddable chat component, there wasn't a way to make it appear completely native to your app. As with most other chat libraries, it's usually at least a tiny bit obvious that the component comes from a third party. And even if it's not obvious to your users, it can still be frustrating to you, as the developer or owner of your app.

For example, what if you don't want to have your chat rendered in a pop-up, but organically on your website instead? What if the chat component you're using doesn't have a feature you want yet, but you know you could build it quickly yourself? What if you use a unique font, and simply want to make sure your chat component matches it?

Take a look at the chat component to the right (or below, on mobile). At first glance, you may have assumed it was just another image/gif, but it's actually a live demo using our latest @papercups-io/chat-builder library. Try sending us a message!

Basically all this library does is abstract away the configuration options, chat state (e.g. messages, customer info), and callbacks (e.g. onSendMessage) which are required to build a chat UI. Currently, it does this in the form of a React "render prop" component, which passes these as props to its child components.

(In case I'm not explaining it well, we happen to be open source. So you can just look at the code for this page right here! 🤓)

Welcome to Papercups 👋

Questions? Feedback? Let us know below!

In code, here's an example of what this might look like if you wanted to keep the "pop-up" UX and just wanted to customize each "section" of it:

import React from 'react';
import {ChatBuilder} from '@papercups-io/chat-builder';
import {Header, Body, Footer, Toggle} from './my/custom/components';

const App = () => {
  const config = {
    title: 'Welcome to Papercups!',
    subtitle: 'Ask us anything in the chat window 😊',
    accountId: '...',
    greeting: 'Hi there! How can I help you?',
    customer: {
      name: 'Test User',
      email: 'test@test.com',
    },
  };

  return (
    <ChatBuilder
      config={config}
      header={({config, state, onClose}) => (
        <Header config={config} state={state} onClose={onClose} />
      )}
      body={({config, state, scrollToRef}) => (
        <Body config={config} state={state} scrollToRef={scrollToRef} />
      )}
      footer={({config, state, onSendMessage}) => (
        <Footer config={config} state={state} onSendMessage={onSendMessage} />
      )}
      toggle={({state, onToggleOpen}) => (
        <Toggle state={state} onToggleOpen={onToggleOpen} />
      )}
    />
  );
};

Below are are some examples we've built using the @papercups-io/chat-builder library.

The first one uses NES.css to render an NES-style (8bit-like) chat UI (source code / live demo). The second one is an example of how you can use the library to mimic Intercom's chat UI, if you happen to be a fan of their design (but not their pricing 😉) (source code / live demo).

NES
Intercom

What's next?

What's next in chat at Papercups? Here are a few of the things we're excited about:

  • Rich message layouts, conversation components (looking at Slack and Facebook Messenger for inspiration)
  • A better UX for managing multiple conversations
  • Better support for automatically handling frequently asked questions
  • Programmatically setting bot playbooks
  • A contact form API, so you can forward messages from your website's contact form to Papercups

What do you want to see us build over the next few months? We'd love to hear from you! Open the chat in the lower right corner and let us know 😊


Posted on April 12, 2021