Grilled Pixels - Portfolio of Adam RobertsGrilled Pixels • Award winning designer and developer
React & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & GatsbyReact & Gatsby
20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years20+ Years
Noteworthy27th November 2021

How using conversational forms can change everything

Roundup –

From early learnings replacing a standard form with something conversational, I'll dive a little deeper into the nuts and crannies...


Making forms more exciting!

Welcome everyone, today I'll be showing you how to set up conversational forms in Gatsby and giving you some information that you need to know. We'll also take a look at one of the most complicated forms I've ever built and how I got around it.

**Disclaimer: If you're a web developer and haven't yet looked at the JAMStack / Gatsby, then this article isn't yet for you, but will be very useful for you in the future. So don't forget to bookmark it and come back later!

It's not AI • It's not AI •
What is a conversational form?

Getting away from the ordinary

Everyone, or at least a large percentage of humans find forms time consuming and boring to fill out. A conversational form takes this dry, common interface and turns it into an engaging conversation.

I've made about 8 of them so far, most very simple and a couple that are very complex. 1 in particular which we'll be talking about here.

So as any form goes, you have an input field asking for your name and you reluctantly fill it out. Same with any other type of field. But with a conversational form, you can make your users feel like they're actually being spoken to by you or your brand. In essence it becomes personal, intuitive and gives users an emotional touchpoint... it's all about UX right? In comparison then, the ordinary form is arguably a bad user experience, no matter how much you style it and make it interactive... you can't really beat the personal 'too and fro' that a conversational form gives your user.

What I like about the conversational forms that I build, is the fact that the written dialogue can be designed. We can ask staight forward questions, we can build conditional flows with multiple paths and outcomes or we can make it give the user information before they're asked a question...


What I'm using...

As you may or may not be aware, I only build in React now (coming from 20 years of traditional development / Wordpress etc) and building a conversational form couldn't be easier (most of the time).

The conversational form technology comes from an experimental project created by SPACE10 and it's constantly being worked on and improved. In 2020 they released the availability of it being used in many different technologies from traditional jquery or vanilla javascript, to react, vue or angular.

One of my original implementations was the vanilla version into React but since the release of the React version, development has been much friendlier. Additionally, with the likes of creating reusable components, the time factor is shortened a fair bit too.

The docs as well as my first few implementations of it, are written as class components. But I've recently converted over to functional components instead so there is less bloat and better performance.

Also because I converted it to functional, I had to add it to my own website on my contact page.

Choosing when to use it...

Look, conversational forms are amazing, period – but, I wouldn't say it fits every use-case. A conversational form on a goverment or law website? Perhaps not. But marketing, brochure, ecommerce, personal or SaaS websites, definitely works.

It also depends on the data you're trying to obtain from the form. If you only need a handful of information, it might not be worth it. With an element of brand in this as well, if you need to be strict in terms of asking for really important information, I wouldn't use it.

But if you want your visitors to feel like their speaking to you and your brand directly and you want to humanise the connection between them and your website, and perhaps you want to share some dialogue in a more human language too, then stop using traditional, boring forms.

Complex conversations • Complex conversations •
Making the complex, simple

Let's take a look at the most complex conversational form I've ever built...

A while ago, I did a project with Levin Riegner to help them with the front-end development of a new website (a new arm of the agency) – Liquid Crystal.

On the project with me, was Daniel Velasquez (known from Codrops) – he created the webgl for the site, while I created the foundation in Gatsby, the light/dark mode, all the interactions and effects etc. This project was really fun to work on.

But the challenge with the contact form is what makes this super complex even while it doesn't feel like it... So on the page, you're presented with 3 questions and when interacted with, it will dynamically change and reorder the content that's presented to you which will suit your interests more.

The answers to these questions, also feed into the contact form and will dynamically change the dialogue you're given to you when you activate the chat.

However, there is also 5 completely different dialogues which you can activate, depending on which button you use to activate it. 5 dialogues, which also change depending on the questions on the page – they only change if the questions are answered, but if they're not, you get asked them again.

The chat window is 1 component which contains the following;

  • 5 chat dialogues (all in separate folders)
  • The conversational form settings
  • Dynamic form headers (we use Formspree, but depending on which dialogue you activate, will change where the data is sent... yep!!)
  • The form markup

Lastly, if you activate the chat via the team member section, it offers a slightly different experience because it appears even more personable. Cool, right?

How da f*ck did you do it?

Here's how to get started...

First of all, I'll assume you've got a gatsby project already opened up in your editor. If not, you'll want to come back after that. This isn't for beginners.

So, I use styled-components – but what I'll do here is write a version where you can take it and style it your way. In fact, I won't provide any styling at all. However, there is quite a bit of 'overriding' you need to do later, FYI.

// Imports
// ------
import React, { useRef, useState } from 'react';
import Avatar from './avatar-image.png';
import Dialogue from './dialogue';
import { observer } from 'mobx-react';

// Styles
// ------
// Add your styles here

// Component
// ------
const ConvoChat = () => {
    // Declare the refs
    const form = useRef(null);
    const staticForm = useRef(null);
    const convoForm = useRef(null);

    // Set the local states
    const [formName] = useState("Contact");
    const [formAction] = useState("ADD_URL_ENDPOINT"); // Eg:

    // Activating the chat
    function activateForm() {
        const { ConversationalForm } = require("conversational-form");
        const form = staticForm.current;

        const cfInstance = new ConversationalForm({
            formEl: staticForm.current,
            context: convoForm.current,
            robotImage: Avatar,
            userImage: false,
            preventAutoFocus: false,
            showProgressBar: true,
            submitCallback: () => {
                const data = new FormData(form);
                const xhr = new XMLHttpRequest();
      , form.action);
                xhr.setRequestHeader("Accept", "application/json");
                xhr.onreadystatechange = () => {
                    if (xhr.readyState !== XMLHttpRequest.DONE) return;
                    if (xhr.status === 200) {
                        cfInstance.addRobotChatResponse("<strong>Thank you message.</strong>");
                    } else {
                        cfInstance.addRobotChatResponse("Something went wrong! 😭");

    let sharedFormProps = {
        name: formName,
        method: "POST",
        action: formAction,

    // Return the component
    return (
        <section ref={form}>
                {/* Static form that builds the interactive conversational form */}
                <form ref={staticForm} {...sharedFormProps}>
                    {/* Honeypot spam filtering */}
                    <input type="text" name="_gotcha" style={{ display: "none" }} />

                    {/* Now we need the form data, which is dynamically driven depending on state selections. */}
                    <Dialogue />

                    {/* The static form also includes the submit button */}
                    <Submit type="submit">Submit</Submit>

                {/* Now for the conversational form, everything above is transpiled and used within the plugin */}
                <form ref={convoForm} {...sharedFormProps}>
                    {/* Honeypot spam filtering */}
                    <input type="text" name="_gotcha" style={{ display: "none" }} />

export default observer(ConvoChat);

This would be your components/chat/index.jsx

Just to explain what's going on here; first we import the neccessary packages and files we need for the chat. In my working version I have a few other things going on, to be able to activate the form in a modal from another component, but I don't want to confuse you with any of that.

You don't necessarily need observer, but I found it useful in my working version too so I've left that in there. It depends if you want this static on a page, or opening up in a modal, I have the latter.

Next, we're using refs and state. The two variables in state hold the name of the form as well as the URL we'll be sending our data to. You can change this to your own preference.

The activateForm() function does all the hard work. This is the settings for the conversational form as well as holds a callback function that gets run when you hit the submit, utilising XHR to send the data. You'll also see that inside this function, we have the following line; const { ConversationalForm } = require("conversational-form"); ... this due to a build error that happens if you import it at the top, so this fixes the window error message you get, feel free to try it.

So in our return (what we're rendering on the page) is in fact 2 forms. The 2 form elements in my real example are ever so slightly different since I'm using style-components. But the most important thing here, is the ref. This is what differientiates the two forms and gets this working.

You'll see the first form holds a hidden field as well as our "data" in <Dialogue /> and a submit button. But it doesn't exist in the second form. This is because the conversational-form package takes what we declare in the first form and mirrors it in to theirs. The conversational form hides the static version and takes over.


Additionally, the first form is what we class as "static", which is absolutely neccessary for Netlify. I think this may be the reason I switched to Formspree, but I'm going to assume they are the same. In essence, for Netlify at least, every single form field that exists in your form, MUST be visible at build time. If any field is not visible at build time, it will not get picked up by the Netlify system, thus in return, the Netlify form tab won't pick up those none-visible fields. So if you ever get the scenario where fields are missing in Netlify, 9 times out of 10, it's because those fields are not static.

Moving on, so next up is the actual data. The fun, the crux, the meat and potatoes... ok whatever. Create a dialogue.jsx in the same folder. See below for a "taster"...

// Imports
// ------
import React from 'react';
import { observer } from 'mobx-react';

// Styles
// ------
import {
} from './styles';

// Reusables
// ------
const Intro = () => (
    <fieldset cf-questions="What's your first name?">
        <cf-robot-message cf-questions="Hello!!<br />I'm excited to start the conversation." />
        <label htmlFor="name">What's your first name?</label>
        <Input required type="text" name="name" id="name" />

const Email = ({ name }) => (
    <fieldset cf-questions="Oh hey {name}, and what's your email?">
        <label htmlFor="email">Oh hey {name}, and what's your email?</label>
        <Input required type="email" name="email" id="email" />

// Component
// ------
const Dialogue = () => {
    return (
            <Intro />
            <Email />

export default observer(Dialogue);

So you can probably see what's going on here. We're setting up each field and declaring the structure of the form at the bottom. Now, I will give a snippet of something more complex shortly, but this here is the very very basic of what you need to get started with it.

Let's break down the fieldset for a moment, because it seems weird if you read it right?

const Intro = () => (
    <fieldset cf-questions="What's your first name?">
        <cf-robot-message cf-questions="Hello!!<br />I'm excited to start the conversation." />
        <label htmlFor="name">What's your first name?</label>
        <Input required type="text" name="name" id="name" />

So what you need to understand is that, within the conversational-form, it will spit out the cf-robot-message's, before the actual question. Also, you can have multiple robot messages too, or you can seperate text with a <br />.

Also, did you notice in that dialogue code above, that I'm passing {name}... yeah, that works too without the need for state management, it's part of the package!

Show me da money!!!

Let's up our game...

As I mentioned above, I wanted to show you a little piece of code, just to highlight what complex really means and what I developed for Liquid Crystal. I can only really do this in a screenshot, so please see the image below...

Just to explain... on the 2 questions on the homepage, if they are answered, it'll set your answer in global state. I then do some conditional questioning to check what has been answered and what hasn't.

You'll see those conditionals in the NOTE... and we have about 4 possibilities. Each conditional, has a different intro sequence and different sets of questions. Oh and... this is just for 1 dialogue. There's quite a few...

{Chat.requestConversation && <DataConversation />}
{Chat.requestDeck && <DataDeck />}
{Chat.requestProposal && <DataProposal />}
{Chat.requestExpert && <DataExpert />}
{Chat.requestIndividual && <DataMember />}

Sounds crazy right? Well, it couldn't have been easier without React and a couple of Red Bulls!


Enough jibber jabber...

I hope that this gives you some insight into how easy it is to set up a conversational form in gatsby and some insight into how complex you can take it. If any readers would like any advice or want to get a further walkthrough, feel free to get in touch.

Share this read on social