Vue Contact Form

Without Backend, Sent via Email (or Slack)

Forms Example

This guide will show you how to make a nice-looking and elegant form with Vue. Contact form creation in Vue shouldn't be a tedious task if you're working on a static/jamstack site.

Often, different guides suggest using backend NodeJS/PHP servers for storing/emailing data. But it's not an absolute rule if you think smart, and your codebase can be a lot cleaner with the fronted-only code.

Create the Vue app

(if you're starting a brand new project)

In case you're starting a brand new project, you'll need some initial steps. One of the most straightforward ways is to use the create-vue-app package. To start:

  • Open the terminal and install the create-vue-app package
    npm install @vue/cli --global
  • Then create your app; this will be your project root folder
    vue create vue-project
  • Go to the directory where you will store your project
    cd ~/vue-project
  • When the installation has finished, you can start the server
    npm run serve

Use your favorite code editor to work with files in ~/vue-project/src. You will be able to make a contact form there.

Please keep in mind that the TailwindCSS example is for demo purposes only, for the production please refer to the TailwindCSS installation.

Embed component into your app, enable styling

Open App.vue in your src folder.

1<template>
2  <div class="app-advanced p-10">
3    <img class="mx-auto" alt="Vue logo" src="../assets/logo.png" />
4
5    <form
6      :action="FORM_ENDPOINT"
7      @submit="handleSubmit"
8      method="POST"
9      target="_blank"
10      class="w-1/2 mx-auto mt-5"
11    >
12      <div class="mb-3 pt-0">
13        <input
14          type="text"
15          placeholder="Your name"
16          name="name"
17          class="
18            px-3
19            py-3
20            placeholder-gray-400
21            text-gray-600
22            relative
23            bg-white bg-white
24            rounded
25            text-sm
26            border-0
27            shadow
28            outline-none
29            focus:outline-none
30            focus:ring
31            w-full
32          "
33          required
34        />
35      </div>
36
37      <div class="mb-3 pt-0">
38        <input
39          type="email"
40          placeholder="Email"
41          name="email"
42          class="
43            px-3
44            py-3
45            placeholder-gray-400
46            text-gray-600
47            relative
48            bg-white bg-white
49            rounded
50            text-sm
51            border-0
52            shadow
53            outline-none
54            focus:outline-none
55            focus:ring
56            w-full
57          "
58          required
59        />
60      </div>
61
62      <div class="mb-3 pt-0">
63        <textarea
64          placeholder="Your message"
65          name="message"
66          class="
67            px-3
68            py-3
69            placeholder-gray-400
70            text-gray-600
71            relative
72            bg-white bg-white
73            rounded
74            text-sm
75            border-0
76            shadow
77            outline-none
78            focus:outline-none
79            focus:ring
80            w-full
81          "
82          required
83        />
84      </div>
85
86      <div class="mb-3 pt-0">
87        <button
88          class="
89            bg-blue-500
90            text-white
91            active:bg-blue-600
92            font-bold
93            uppercase
94            text-sm
95            px-6
96            py-3
97            rounded
98            shadow
99            hover:shadow-lg
100            outline-none
101            focus:outline-none
102            mr-1
103            mb-1
104            ease-linear
105            transition-all
106            duration-150
107          "
108          type="submit"
109        >
110          Send a message
111        </button>
112      </div>
113    </form>
114
115    <div v-if="submitted" class="text-center mt-10">
116      <h2 class="text-2xl">Thanks you!</h2>
117      <div class="text-md">We'll be in touch soon.</div>
118    </div>
119  </div>
120</template>
121
122<script>
123export default {
124  name: "App",
125  data: () => ({
126    submitted: false,
127    FORM_ENDPOINT: endpointUrl,
128  }),
129
130  methods: {
131    handleSubmit() {
132      this.submitted = true;
133    },
134  },
135
136  // To Add Tailwind
137  beforeCreate() {
138    if (document) {
139      const stylesheet = document.createElement("link");
140      stylesheet.rel = "stylesheet";
141      stylesheet.href =
142        "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css";
143
144      document.head.appendChild(stylesheet);
145    }
146  },
147};
148</script>

Create the free HeroTofu forms backend

Head over to herotofu.com and create an account. It will handle all the boring and complex form submission process work for you. You'll get 14 days of the free trial at first, and later you can leave it with the free forever plan. For the sole purpose of the contact form, it's usually more than enough.

HeroTofu registration is straightforward. Fill in the basic fields and then confirm your email address.

Herotofu signup

Once you have confirmed your email address, go to app.herotofu.com/forms to create your first form. Fill in the form name and add your preferred email address where you'd like to receive your form submits. Slack and Zapier are also options, but you need to pay for them once the trial is over.

You'll get the form endpoint URL once you hit submit, so remember to copy it.

Herotofu Forms List

Use the created forms backend in your contact form

Once again, open the App.vue file and fill in the form endpoint URL. You need to change the FORM_ENDPOINT variable.

Done! Go ahead and test your contact form submission! You don't need to do any backend email work, as HeroTofu will handle everything.

Bonus: advanced Vue implementation

As you can see, the implementation is basic, and you only get those fields on the contact form that are visible on the HTML. That might not work if you need a more customized flow. You'll need to adjust the handleSubmit handler function to inject extra data dynamically. Good examples could be the user id, the selected plan, or some meta-information of site usage. It should make an ajax call to the FORM_ENDPOINT instead of the regular form submits. Here's how it could look like in practice.

1<template>
2  <div class="app-advanced p-10">
3    <img class="mx-auto" alt="Vue logo" src="../assets/logo.png" />
4
5    <form
6      :action="FORM_ENDPOINT"
7      @submit="handleSubmit"
8      method="POST"
9      target="_blank"
10      class="w-1/2 mx-auto mt-5"
11    >
12      <div class="mb-3 pt-0">
13        <input
14          type="text"
15          placeholder="Your name"
16          name="name"
17          class="
18            px-3
19            py-3
20            placeholder-gray-400
21            text-gray-600
22            relative
23            bg-white bg-white
24            rounded
25            text-sm
26            border-0
27            shadow
28            outline-none
29            focus:outline-none
30            focus:ring
31            w-full
32          "
33          required
34        />
35      </div>
36
37      <div class="mb-3 pt-0">
38        <input
39          type="email"
40          placeholder="Email"
41          name="email"
42          class="
43            px-3
44            py-3
45            placeholder-gray-400
46            text-gray-600
47            relative
48            bg-white bg-white
49            rounded
50            text-sm
51            border-0
52            shadow
53            outline-none
54            focus:outline-none
55            focus:ring
56            w-full
57          "
58          required
59        />
60      </div>
61
62      <div class="mb-3 pt-0">
63        <textarea
64          placeholder="Your message"
65          name="message"
66          class="
67            px-3
68            py-3
69            placeholder-gray-400
70            text-gray-600
71            relative
72            bg-white bg-white
73            rounded
74            text-sm
75            border-0
76            shadow
77            outline-none
78            focus:outline-none
79            focus:ring
80            w-full
81          "
82          required
83        />
84      </div>
85
86      <div class="mb-3 pt-0">
87        <button
88          class="
89            bg-blue-500
90            text-white
91            active:bg-blue-600
92            font-bold
93            uppercase
94            text-sm
95            px-6
96            py-3
97            rounded
98            shadow
99            hover:shadow-lg
100            outline-none
101            focus:outline-none
102            mr-1
103            mb-1
104            ease-linear
105            transition-all
106            duration-150
107          "
108          type="submit"
109        >
110          Send a message
111        </button>
112      </div>
113    </form>
114
115    <div v-if="status" class="text-center mt-10">
116      <h2 class="text-2xl">Thanks you!</h2>
117      <div class="text-md">{{ status }}</div>
118    </div>
119  </div>
120</template>
121
122<script>
123import { ref } from "vue";
124export default {
125  setup() {
126    const FORM_ENDPOINT = ref(endpointUrl);
127    const status = ref();
128
129    function handleSubmit(e) {
130      e.preventDefault();
131
132      // Anything you need to inject dynamically
133      const injectedData = {
134        DYNAMIC_DATA_EXAMPLE: 123,
135      };
136
137      const inputs = e.target.elements;
138      const data = {};
139
140      inputs.forEach((inp) => {
141        if (inp.name) {
142          data[inp.name] = inp.value;
143        }
144      });
145
146      Object.assign(data, injectedData);
147
148      fetch(FORM_ENDPOINT.value, {
149        method: "POST",
150        headers: {
151          Accept: "application/json",
152          "Content-Type": "application/json",
153        },
154        body: JSON.stringify(data),
155      })
156        .then((response) => {
157          // It's likely a spam/bot request, so bypass it to validate via captcha
158          if (response.status === 422) {
159            Object.keys(injectedData).forEach((key) => {
160              const el = document.createElement("input");
161              el.type = "hidden";
162              el.name = key;
163              el.value = injectedData[key];
164
165              e.target.appendChild(el);
166            });
167
168            e.target.submit();
169            throw new Error("Please finish the captcha challenge");
170          }
171
172          if (response.status !== 200) {
173            throw new Error(response.statusText);
174          }
175
176          return response.json();
177        })
178        .then(() => (status.value = "We'll be in touch soon."))
179        .catch((err) => (status.value = err.toString()));
180    }
181
182    return { status, handleSubmit, FORM_ENDPOINT };
183  },
184
185  // To Add Tailwind
186  beforeCreate() {
187    if (document) {
188      const stylesheet = document.createElement("link");
189      stylesheet.rel = "stylesheet";
190      stylesheet.href =
191        "https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css";
192
193      document.head.appendChild(stylesheet);
194    }
195  },
196};
197</script>

The core idea is that you send a POST request to the FORM_ENDPOINT of your contact form. But it doesn't matter which way you do it. It can be a regular form submit with ajax handler upon form submission or a completely different request that doesn't involve HTML forms at all

Treat it as a REST API because as long as you're sending POST, you'll be good to go. Your vue form submission will reach your inbox.

 
Contact Form Backend -- Start NowFree and Paid plans