Home / Articles / Developing a Netlify Contact Form using Alpine.js

Developing a Netlify Contact Form using Alpine.js

October 31, 2020
7 min. read

The most common request to a simple landing or a simple company web page is to provide a simple contact form. A simple feature to add, isn’t it? Well, not so much. For a simple static web deployment, you need to provide a backend infrastructure with email support. Fortunately, there is an easy solution for it.

What is the Netlify?

Netlify is a service platform that provides static deployment to your website with many features such as custom domains, https, git deployment, serverless functions, and for us, a form service.

How does it work?

For a simple HTML form, append the netlify attribute to its form tag:

<form name="contact" netlify>
....

</form>

Then, every time a user submits the form, it will be intercepted from Netlify, the form info will appear in your site dashboard, and the user will be directed to a success page. Furthermore, it may be configured to notify yourself of the submissions using email or any webhook and add three-level spam filtering.

Spam Prevention

The spam prevention systems consist of:

  • a spam detection system,

  • an optional honeypot field, which is a hidden form field that lures bot users into completing a field that human users can’t see, and

  • an optional reCAPTCHA spam prevention.

    If you do not want to use any javascript, the following HTML code will be working when it will be deployed:

The first takeout from the above code is how to compose the <form> tag:

<form name="contact" method="POST" data-netlify="true"
        netlify-honeypot="bot-field" data-netlify-recaptcha="true">

We named the form (name="contact"), we defined it as netlify form with the data-netlify="true" attribute and add two optional spam-prevention layers: the honeypot (netlify-honeypot="bot-field") and the Google reCAPTCHA 2 (data-netlify-recaptcha="true").

The netlify-honeypot attribute is working by adding a hidden input form element, which will be submitted as empty:

<p hidden><label>ignore: <input name="bot-field" /></label></p>

The idea behind this spam-prevention schema is that a bot will see the input element, and it will fill it. Therefore, if the form is submitted with a non-empty value, then it will be rejected. Take note that the name of the honeypot bot-field can be anything; just defined it in the <form> tag.

The Google reCAPTCHA 2 will be showing up by adding the following inside the <form> tag:

<div data-netlify-recaptcha="true"></div>

The above code will work on any HTML page that is deploying in the netlify server. In fact, by using the html5 form validation attributes, it can provide client-based form validation, too.

However, if the client-based form validation is desired to be done using javascript, how the submission is working? And, especially, using the Alpine.js?

Alpine.js

Alpine.js is a reactive framework, very similar to Vue, with two very distinct differences. The first is the lower cost, as it does not need a bundler. Just adding a script tag (~8KB gzip and minified) is sufficient. The second one involves ignoring the famous virtual DOM strategy and adopts for sprinkling the code directly on your HTML. In that respect, it is very similar to Tailwind but for javascript instead of styling.

The installation is simple enough just add a script to the end of your <head> section:

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script>

These distinct characteristics make it more suitable for jQuery replacement for smaller tasks than Vue.

For more information, you may check our introductory article about Alpine.js.

Initially, we recreate the HTML-only form code and hide it. The hidden form acts as a stand-in for the Alpine.js form component, so the netlify bot understands what to expect to receive from the fetch submission.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 <form
      hidden
      name="contact"
      method="POST"
      data-netlify="true"
      netlify-honeypot="bot-field"
      data-netlify-recaptcha="true"
    >
      <label><input type="text" name="name" /></label>
      <label> <input type="email" name="email" /></label>
      <label><textarea name="message"></textarea></label>
</form>

Next, we initialize our component using x-data with the required data object for form validation. The x-data attribute plays the same role as the data Vue component property. The data object properties, as in Vue, are reactive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<form
      x-data="{
                name : '',
                nameDirty: false,
                message: '',
                messageDirty: false,
                email: '',
                emailDirty: false,
               }"
      x-on:submit.prevent="
                  nameDirty = true;
                  emailDirty = true;
                  messageDirty = true;
                  submitMessage(name, email, message);"
      id="contact"
    >

The x-on attaches an event listener to the form submit event.

Next, to validate and check the value of an input form element component, the following code is used:

<p><label>Full Name: <input x-model="name" x-on:blur="nameDirty = true" type="text" name="name"/></label></p>
<p x-show.transition="nameDirty && name.length == 0" style="color: red" x-cloak>
        Please fill out your full name.
</p>

The x-model attribute adds a “two-way data binding” to the input element. Consequently, the input element’s value will be in sync with the name variable in the data object. When the input lost focus, the nameDirty variable is updated as true.

If nameDirty is true and the name variable is empty, a warning message is shown using the x-show attribute. The x-show attribute toggles the display: none; style attribute on the element depending on the expression output.

The transition directive is a convenience API offered by Alpine.js, which applies CSS transitions to the element. From the documentation, the transition directive is a simultaneous fade and scale: opacity, scale: 0.95, timing-function: cubic-bezier(0.4, 0.0, 0.2, 1), duration-in: 150ms, duration-out: 75ms

Form Submission

The function submitMessage takes as input the contents of name, email and message variables, validate them, and if it is correct, it submits the form contents using the fetch API.

Note that the AJAX request’s content must be URL-encoded, as the Netlify forms do not support JSON form data currently. The URL-encoded form can be easily created by using the URLSearchParams utility method:

let formElement = document.getElementById("contact");
let body = new URLSearchParams(new FormData(formElement)).toString();

The final complete submitMessage function is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function submitMessage(name, email, message) {
    if (!name || name.length == 0 || !email || !isEmail || !message || message.length == 0) {
      return;
    }
    let formElement = document.getElementById("contact");
    let body = new URLSearchParams(new FormData(formElement)).toString();
    return fetch("/", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: body,
    })
      .then((response) => {
        if (response.ok) {
          formElement.reset();
          alert("Thank you for your message!");
        } else {
          throw new Error(`Something went wrong: ${response.statusText}`);
        }
      })
      .catch((error) => console.error(error));
  }

The final code using Alpine.js for client-based form input validation and sending the form to netlify using the Fetch API is:

Altogether, I think the Netlify form simplifies the usage of form in your web page. Of course, under the assumption to host your web app in their hosting service.

Share:

comments powered by Disqus

Also Read:

Vue’s primary motivation behind the introduction of Composition API was a cost-free mechanism for reusing logic between multiple components or apps. Is there a way to use that approach for AlpineJs without sacrificing its simplicity?
Most uncomplicated today web sites, like this blog, for example, or a landing page, do not need extensive javascript frameworks with complicated build steps. The Alpine.js framework is a drop-in library that replaces the jQuery query-based (imperative) approach with the tailwindcss-inspired declarative approach on the DOM using a familiar Vue-like syntax.
One of the constant requests a freelancer programming has is constructing a landing page for a project/company/research project etc. Therefore, it is better to have an already starter template adopted as a base to iterate upon it quickly.