Ghost + Formspree.io

Ghost + Formspree.io

In my last post, I wanted to include a form for feedback directly in the post. The Ghost platform doesn’t have a built-in forms feature (beyond the form for subscribing). Below are some details about the direction I went, as well as a sample and some learnings in case you’re trying to do something similar in the future.

Several options around embedding form submission services with a Ghost site. I picked Formspree, though most seem to have similar functionality. Add an HTML block to the post, then drop in the form code.

To be specific, I started with the AJAX submission sample from Formspree, not the simplest HTML form submission. While slightly more technical, I much prefer keeping the visitor on the site after the form is submitted. Either one looks clunky and bland.

Screenshot of an unformatted HTML form
The default formatting sucks.

t’s not fair to say the formatting sucks…there isn’t any formatting by default. Either way, it needed work. I poked around a bit at some of the templates on the Formspree site but they’re just as inconsistently styled. I remember when I had real websites with forms looked like that.

I decided to do my own styling

I did some checking for an easy-button option. My first attempts to find something didn’t get me there.

Ghost has documentation for this integration specifically, but it doesn’t offer any guidance around what to leverage from the out of the box themes. Well, it does mention the approaches to styling, but nothing specific I could copy paste.

"The default Formspree code includes some inline styling."--pretty minimal-- "These styles can be edited to suit your site’s design directly in the form code, in your site’s theme, or using code injection."

I picked option 1, to add styling inline directly in the form code. Plus added in a couple of little things like the default text in the input boxes and hidden subject field.

Looking better

Screenshot of the HTML form with some formatting
Certainly room for improvement, but quite satisfied with the result!

In case you’d like to borrow from this work, here’s the sample:

<form id="my-form" action="https://formspree.io/f/myform" method="POST">
    <label for="name">Name:</label>
    <input type="text" name="name" id="name" style="width: 100%; padding: 10px; margin-bottom: 10px;">

    <label for="email">Email:</label>
    <input type="email" name="_replyto" id="email-address" style="width: 100%; padding: 10px; margin-bottom: 10px;">

    <label for="message">Message:</label>
    <textarea name="message" id="message" placeholder="Any specific details?" style="width: 100%; padding: 10px; margin-bottom: 10px;"></textarea>

    <input type="hidden" name="_subject" id="email-subject" value="MCA Blog: RELEVANT SUBJECT">
 
    <button id="my-form-button" style="background-color: #1983FF; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer;">Submit</button>
    <p id="my-form-status"></p>
  </form>
  <!-- Place this script at the end of the body tag -->
  <script>
      var form = document.getElementById("my-form");
      
      async function handleSubmit(event) {
        event.preventDefault();
        var status = document.getElementById("my-form-status");
        var data = new FormData(event.target);
        fetch(event.target.action, {
          method: form.method,
          body: data,
          headers: {
              'Accept': 'application/json'
          }
        }).then(response => {
          if (response.ok) {
            status.innerHTML = "Thanks for your submission!";
            form.reset()
          } else {
            response.json().then(data => {
              if (Object.hasOwn(data, 'errors')) {
                status.innerHTML = data["errors"].map(error => error["message"]).join(", ")
              } else {
                status.innerHTML = "Oops! There was a problem submitting your form"
              }
            })
          }
        }).catch(error => {
          status.innerHTML = "Oops! There was a problem submitting your form"
        });
      }
      form.addEventListener("submit", handleSubmit)
  </script>

I use an out of the box theme, which is an intentional restriction to make sure I don’t waste time on a theme (instead I focus on content).

Then I used GitHub Copilot Chat to turn my inline styling into a stylesheet that I injected into my post.

Not too hard to do manually, but a tedious task that’s easy to have little errors doing by hand. The result:


<form id="my-form" action="https://formspree.io/f/myform" method="POST">
  <label for="name">Name:</label>
  <input type="text" name="name" id="name">

  <label for="email">Email:</label>
  <input type="email" name="_replyto" id="email-address">

  <label for="message">Message:</label>
  <textarea name="message" id="message" placeholder="Any specific details?"></textarea>

  <input type="hidden" name="_subject" id="email-subject" value="Blog Request: More about new FY plan and process">

  <button id="my-form-button">Submit</button>
  <p id="my-form-status"></p>
</form>

Then the style goes into the code injection panel for the post:

<style>
    label {
  display: block;
  margin-bottom: 10px;
}

input[type="text"],
input[type="email"],
textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
}

button {
  background-color: #1983FF;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

Still happy with my intentional limitations

I’m constantly aware that there are much more elegant and sophisticated technical options available for solving this problem.

I haven’t been sucked into theme design. Especially when I was running on Wordpress, I would lose hours doing design tweaks to everything in my theme. Even when I paid for premium themes…I seemed to also get more options and more distraction. I’m keeping it simple.

I’m future proofed. The service offers scalability of the service that I don’t need to plan for. When I have hundreds of people submitting forms to my little site I’ll gladly pay a few extra bucks a month to not have to fuss over the app. I can also switch to a different provider (or even build my own solution) in the future if need be.

I get additional nice-to-haves like a dashboard, notifications, and integration capabilities with the service, and a whole bunch of other little features that I wouldn’t have thought of on my own. I don’t need to build any of that out. PaaS, baby.

I also find it fun to puzzle some of these little things, without taking on too much scope. It’s inherently limiting in some ways, but I will never have time to do everything. This one’s an easy choice.

For now, this is where I’ll leave it.