FormThunder isn't open yet. This site describes the service as it will run at launch. Find out more

FormThunder
Menu

Add an Alpine.js contact form with no build step

An Alpine.js contact form fits inside your HTML with no build step. Keep the status in x-data, intercept the submit with @submit.prevent, post to FormThunder with fetch, and show the outcome with x-show. FormThunder stores the submission and emails you. If Alpine fails to load, the form still submits normally, because it keeps its action URL.

Updated

The Alpine.js code

contact.html
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>

<form
  action="https://formthunder.com/submit"
  method="POST"
  x-data="{ status: 'idle' }"
  @submit.prevent="
    status = 'sending';
    fetch($el.action, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify(Object.fromEntries(new FormData($el))),
    })
      .then((r) => r.json())
      .then((d) => { status = d.success ? 'sent' : 'error'; if (d.success) $el.reset(); })
      .catch(() => (status = 'error'));
  "
>
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1">
  <label>Name <input name="name" required></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <button :disabled="status === 'sending'">Send</button>
  <p x-show="status === 'sent'" x-cloak role="status">Thanks, we got your message.</p>
  <p x-show="status === 'error'" x-cloak role="alert">That didn't send. Please try again.</p>
</form>

Replace YOUR_ACCESS_KEY with your free key. It's public by design, so it's safe in client code.

Notes for Alpine.js

  • $el is the form here because the directive sits on the form. Move the handler to a child and you'd need $refs.
  • Add [x-cloak] { display: none } to your CSS so the messages don't flash while Alpine starts.
  • Once the handler grows past a few lines, move it to Alpine.data('contact', ...) in a script tag. A strict Content Security Policy needs the CSP build of Alpine anyway.

Or have your AI add it

Connect the MCP server, then ask. The agent creates the form and writes the code for you, and it can send a real test when it's done.

You, to your AI
Add a FormThunder contact form to this Alpine.js site that emails hello@example.com. Send a test when you're done.

Questions

Does this work with Hugo, Jekyll or Eleventy?

Yes. Alpine runs in the browser, so any static generator can output this HTML. Their pages show how to template the key.

Why use Alpine rather than a plain script?

Mostly when Alpine is already on the page. If it isn't, the plain JavaScript version is about the same length.

Get your Alpine.js form sending

The free plan covers 250 submissions a month, with MCP, the CLI and the brand kit included.