Skip to content
FormSubmit

Svelte

How to build a Svelte or SvelteKit contact form

A Svelte 5 contact form component using runes that submits to a hosted form backend — works with adapter-static.

1 min readUpdated Sep 28, 2026

For Svelte and SvelteKit, create a component that posts FormData to your FormSubmit endpoint with fetch and stores the status in a $state rune. It works with adapter-static, so you can host on GitHub Pages, Netlify, Vercel or Cloudflare Pages with no server.

Component (Svelte 5)

src/lib/ContactForm.svelte:

src/lib/ContactForm.svelte
<script>
  const ENDPOINT = "https://formsubmit.app/f/YOUR_FORM_ID";
  let status = $state("idle");
  let error = $state("");
  const renderedAt = Date.now();

  async function onsubmit(e) {
    e.preventDefault();
    status = "sending";
    const data = new FormData(e.currentTarget);
    data.set("_ts", String(renderedAt));
    try {
      const res = await fetch(ENDPOINT, { method: "POST", body: data, headers: { Accept: "application/json" } });
      const json = await res.json();
      if (!res.ok || !json.ok) throw new Error(json.error?.message ?? "Something went wrong");
      status = "success";
    } catch (err) {
      error = err.message;
      status = "error";
    }
  }
</script>

{#if status === "success"}
  <p class="text-green-700">Thanks! We'll be in touch soon.</p>
{:else}
  <form {onsubmit} class="mx-auto max-w-lg space-y-5">
    <div class="space-y-1.5">
      <label for="fs-name" class="block text-sm font-medium text-gray-900">Name</label>
      <input id="fs-name" type="text" name="name" placeholder="Jane Doe" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20">
    </div>
    <div class="space-y-1.5">
      <label for="fs-email" class="block text-sm font-medium text-gray-900">Email</label>
      <input id="fs-email" type="email" name="email" placeholder="jane@example.com" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20">
    </div>
    <div class="space-y-1.5">
      <label for="fs-message" class="block text-sm font-medium text-gray-900">Message</label>
      <textarea id="fs-message" name="message" rows="5" placeholder="How can we help?" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"></textarea>
    </div>
    <!-- Honeypot: leave this hidden field empty to catch bots -->
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    {#if status === "error"}<p class="text-sm text-red-600">{error}</p>{/if}
    <button type="submit" disabled={status === "sending"} class="inline-flex w-full items-center justify-center rounded-lg bg-gray-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50">
      {status === "sending" ? "Sending…" : "Send"}
    </button>
  </form>
{/if}

Route

src/routes/contact/+page.svelte
<script>
  import ContactForm from "$lib/ContactForm.svelte";
</script>

<svelte:head><title>Contact</title></svelte:head>

<h1>Contact</h1>
<ContactForm />

Progressive enhancement

Add action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST" to the <form>. If JavaScript hasn't loaded, the browser posts normally and FormSubmit redirects to your thank-you page; once hydrated, the onsubmit handler takes over.

Svelte 4

Replace $state with plain let variables and onsubmit with on:submit|preventDefault. The fetch logic is identical.

Next steps

Add your domain to the allowlist, connect Slack and review spam settings.

Frequently asked questions

Why not use SvelteKit form actions?

Form actions need a server runtime and you still have to send email and handle spam yourself. Posting to FormSubmit works with adapter-static and gives you an inbox, spam filtering and integrations.

Does it support progressive enhancement?

Yes. Add action and method to the form so it works before hydration.

Keep reading

Your form backend is 60 seconds away

Sign in with Google, create a form, paste the endpoint. Free forever for small sites — no credit card.