Back to blog
TutorialVueSvelteSvelteKit

Form Integration Guide for Vue and Svelte

7 min read

Vue 3 (Composition API)

Basic integration

<script setup>
import { ref } from "vue";

const status = ref("idle"); // idle | submitting | success | error
const errorMsg = ref("");

async function handleSubmit(event) {
  status.value = "submitting";
  errorMsg.value = "";

  try {
    const response = await fetch("https://inputhaven.com/api/v1/submit", {
      method: "POST",
      body: new FormData(event.target),
      headers: { Accept: "application/json" },
    });

    if (response.ok) {
      status.value = "success";
      event.target.reset();
    } else {
      const data = await response.json().catch(() => null);
      errorMsg.value = data?.error || "Something went wrong.";
      status.value = "error";
    }
  } catch {
    errorMsg.value = "Network error. Please try again.";
    status.value = "error";
  }
}
</script>

<template>
  <div v-if="status === 'success'">
    <h3>Message sent!</h3>
    <p>Thank you. We'll respond soon.</p>
    <button @click="status = 'idle'">Send another</button>
  </div>

  <form v-else @submit.prevent="handleSubmit">
    <input type="hidden" name="_form_id" value="your-form-id" />

    <!-- Honeypot -->
    <div style="position: absolute; left: -9999px" aria-hidden="true">
      <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" />
    </div>

    <div>
      <label for="name">Name</label>
      <input type="text" id="name" name="name" required />
    </div>

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

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

    <p v-if="errorMsg" role="alert" class="error">{{ errorMsg }}</p>

    <button type="submit" :disabled="status === 'submitting'">
      {{ status === "submitting" ? "Sending..." : "Send Message" }}
    </button>
  </form>
</template>

Nuxt 3

In Nuxt, the same component works as-is. Place it in components/ContactForm.vue and use it anywhere:

<template>
  <ContactForm />
</template>

No server middleware or API routes needed.

Svelte

Basic integration

<script>
  let status = "idle"; // idle | submitting | success | error
  let errorMsg = "";

  async function handleSubmit(event) {
    status = "submitting";
    errorMsg = "";

    try {
      const response = await fetch("https://inputhaven.com/api/v1/submit", {
        method: "POST",
        body: new FormData(event.target),
        headers: { Accept: "application/json" },
      });

      if (response.ok) {
        status = "success";
        event.target.reset();
      } else {
        const data = await response.json().catch(() => null);
        errorMsg = data?.error || "Something went wrong.";
        status = "error";
      }
    } catch {
      errorMsg = "Network error. Please try again.";
      status = "error";
    }
  }
</script>

{#if status === "success"}
  <div>
    <h3>Message sent!</h3>
    <p>Thank you. We'll respond soon.</p>
    <button on:click={() => (status = "idle")}>Send another</button>
  </div>
{:else}
  <form on:submit|preventDefault={handleSubmit}>
    <input type="hidden" name="_form_id" value="your-form-id" />

    <!-- Honeypot -->
    <div style="position: absolute; left: -9999px;" aria-hidden="true">
      <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" />
    </div>

    <div>
      <label for="name">Name</label>
      <input type="text" id="name" name="name" required />
    </div>

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

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

    {#if errorMsg}
      <p role="alert" class="error">{errorMsg}</p>
    {/if}

    <button type="submit" disabled={status === "submitting"}>
      {status === "submitting" ? "Sending..." : "Send Message"}
    </button>
  </form>
{/if}

SvelteKit

In SvelteKit, you can also use progressive enhancement with SvelteKit's use:enhance:

<script>
  // Same as above — SvelteKit doesn't require any special handling
  // for client-side form submission to external APIs
</script>

SvelteKit's form actions are for server-side processing within your app. For external form backends like InputHaven, use client-side fetch as shown above.

TypeScript support

Both Vue and Svelte support TypeScript. The form data types are simple:

interface SubmissionResponse {
  success: boolean;
  message?: string;
  submissionId?: string;
}

interface ErrorResponse {
  error: string;
}

For full type safety with InputHaven's API, generate a typed client from our OpenAPI specification at /openapi.yaml.

Using @inputhaven/js

For framework-agnostic integration, use our JavaScript package:

npm install @inputhaven/js
import { InputHaven } from "@inputhaven/js";

const client = new InputHaven("your-form-id");

const result = await client.submit({
  name: "Jane Doe",
  email: "jane@example.com",
  message: "Hello from Vue/Svelte!",
});

if (result.success) {
  // Submission successful
} else {
  // Handle error: result.error
}

This works in Vue, Svelte, or any JavaScript environment.

Ready to try InputHaven?

500 free submissions/month. No credit card required.

Get Started Free