In Astro, the easiest contact form is a plain .astro component whose <form> posts to a FormSubmit endpoint. It ships zero JavaScript, works with Astro's default static output (no SSR adapter), and you still get email notifications, spam filtering and integrations.
1. Create an endpoint
Sign in, create a form and copy the endpoint URL.
2. Create the component
src/components/ContactForm.astro:
---
// src/components/ContactForm.astro
const endpoint = "https://formsubmit.app/f/YOUR_FORM_ID";
---
<form action={endpoint} method="POST" 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">
<input type="hidden" name="_redirect" value={new URL("/thanks", Astro.url).href} />
<button type="submit" 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">Send</button>
</form>The _redirect field uses Astro.url to build an absolute URL to /thanks on whatever domain the page is served from — handy for preview deployments.
3. Create the thank-you page
---
import Layout from "../layouts/Layout.astro";
---
<Layout title="Thanks!">
<h1>Thanks — we got your message.</h1>
<p>We'll reply within one business day.</p>
</Layout>4. Use it
---
import Layout from "../layouts/Layout.astro";
import ContactForm from "../components/ContactForm.astro";
---
<Layout title="Contact">
<h1>Contact</h1>
<ContactForm />
</Layout>Inline success without leaving the page
Add a small script to the component. Astro bundles and deduplicates it automatically:
<script>
const form = document.querySelector("form[data-fs]") as HTMLFormElement;
const renderedAt = Date.now();
form?.addEventListener("submit", async (e) => {
e.preventDefault();
const data = new FormData(form);
data.set("_ts", String(renderedAt));
const res = await fetch(form.action, { method: "POST", body: data, headers: { Accept: "application/json" } });
const json = await res.json();
form.outerHTML = json.ok ? `<p>${json.message}</p>` : `<p role="alert">${json.error.message}</p>`;
});
</script>(Add data-fs to the <form> element.)
Islands
Prefer a framework component? Export React, Vue or Svelte from the form generator and render it with client:visible so it only hydrates when scrolled into view.
Before launch
Add your domain to the allowlist, and consider connecting Slack so new messages reach your team immediately.