Vue 3 Contact Form

A Vue 3 component using the Composition API (ref, async/await) for form submission. Includes reactive state for loading, success, and error feedback. Works with Nuxt 3, Astro, or any Vue 3 project. For a full walkthrough, see the Vue contact form guide.

Vue 3 Contact Form preview

Template Code

HTML
<script setup>
import { ref } from 'vue'

const status = ref('idle') // idle | sending | success | error

async function handleSubmit(e) {
  status.value = 'sending'
  const form = e.target
  const data = new FormData(form)

  try {
    const res = await fetch(form.action, { method: 'POST', body: data })
    if (res.ok) {
      status.value = 'success'
      form.reset()
    } else {
      status.value = 'error'
    }
  } catch {
    status.value = 'error'
  }
}
</script>

<template>
  <p v-if="status === 'success'" style="color: #16a34a; font-weight: 600">
    Message sent! We'll be in touch.
  </p>

  <form
    v-else
    action="https://app.formwit.com/api/s/YOUR_FORM_ID"
    method="POST"
    @submit.prevent="handleSubmit"
  >
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />

    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />

    <label for="message">Message</label>
    <textarea id="message" name="message" required></textarea>

    <!-- Honeypot spam protection -->
    <input type="text" name="_gotcha" style="display: none" tabindex="-1" autocomplete="off" />

    <button type="submit" :disabled="status === 'sending'">
      {{ status === 'sending' ? 'Sending...' : 'Send Message' }}
    </button>
    <p v-if="status === 'error'" style="color: #dc2626">Something went wrong. Please try again.</p>
  </form>
</template>

Want to customize?

Build your own form visually with our free HTML form generator.

Try the Form Generator →

Use cases

  • Vue 3 single-page applications
  • Nuxt 3 static or server-rendered sites
  • Astro sites with Vue islands
  • Vue-based dashboards and admin panels

Customization tips

  • Add Tailwind classes or scoped <style> for styling
  • Add a v-model on each input for two-way data binding if you need access to field values
  • Add props for formId to make the component reusable across multiple forms
  • Use defineEmits to emit a submitted event for parent component integration

Related guide

Want a step-by-step walkthrough? Read the full Vue Contact Form Guide.

Related templates

Frequently asked questions

Does this work with Nuxt 3?

Yes. Save it as a .vue file in your components/ directory and use it in any page or layout. No additional configuration needed.

Can I use this with the Options API?

Yes. Move the ref to data() and handleSubmit to methods. The template stays the same.

How do I add form validation?

HTML5 attributes (required, type="email") provide basic validation. For advanced validation, use VeeValidate or check field values in the submit handler before calling fetch.

Get your form working in 30 seconds

  • No credit card required
  • Unlimited forms
  • 100 submissions/month free
Get Started Free