Skip to content

Alpine.js Forms

Alpine.js is a lightweight frontend library that offers reactive binding and declarative syntax without the complexity of larger frameworks like React or Vue. It is a popular choice for static sites, Jekyll, Astro, or WordPress projects.

Using Alpine.js, you can build contact forms that submit data asynchronously via AJAX, preventing page redirects and providing instant user feedback.


Here is a copy-pasteable example of an Alpine.js form. It handles input binding, displays status alerts, and submits values using fetch().

<!-- Load Alpine.js from CDN (if not already included in your project) -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
<div
x-data="{
fields: {
name: '',
email: '',
message: '',
_honeypot: ''
},
status: 'idle', // 'idle' | 'loading' | 'success' | 'error'
errorMessage: '',
async submitForm() {
this.status = 'loading';
this.errorMessage = '';
// Honeypot spam bot check
if (this.fields._honeypot) {
this.status = 'success';
this.resetForm();
return;
}
try {
const response = await fetch('https://api.lucidforms.co/f/{your-form-id}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(this.fields)
});
const data = await response.json();
if (response.ok) {
this.status = 'success';
this.resetForm();
} else {
this.status = 'error';
this.errorMessage = data.error || 'Form submission failed. Please try again.';
}
} catch (err) {
this.status = 'error';
this.errorMessage = 'A network error occurred. Please check your connection.';
}
},
resetForm() {
this.fields.name = '';
this.fields.email = '';
this.fields.message = '';
this.fields._honeypot = '';
}
}"
style="max-width: 400px; margin: 0 auto; font-family: sans-serif;"
>
<h2>Contact Us</h2>
<!-- Success Message -->
<template x-if="status === 'success'">
<div style="color: green; background-color: #e8f5e9; padding: 10px; border-radius: 4px; margin-bottom: 15px;">
✓ Thank you! Your message was submitted successfully.
</div>
</template>
<!-- Error Message -->
<template x-if="status === 'error'">
<div style="color: red; background-color: #ffebee; padding: 10px; border-radius: 4px; margin-bottom: 15px;">
<span x-text="errorMessage"></span>
</div>
</template>
<form x-on:submit.prevent="submitForm">
<!-- Honeypot bot trap (hidden) -->
<input type="text" x-model="fields._honeypot" style="display: none;" tabindex="-1" autocomplete="off" />
<div style="margin-bottom: 12px;">
<label for="name" style="display: block; margin-bottom: 4px; font-weight: 500;">Name</label>
<input
type="text"
id="name"
x-model="fields.name"
required
style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"
/>
</div>
<div style="margin-bottom: 12px;">
<label for="email" style="display: block; margin-bottom: 4px; font-weight: 500;">Email</label>
<input
type="email"
id="email"
x-model="fields.email"
required
style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"
/>
</div>
<div style="margin-bottom: 16px;">
<label for="message" style="display: block; margin-bottom: 4px; font-weight: 500;">Message</label>
<textarea
id="message"
x-model="fields.message"
required
rows="4"
style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;"
></textarea>
</div>
<button
type="submit"
x-bind:disabled="status === 'loading'"
style="padding: 10px 16px; background-color: #4f46e5; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;"
>
<span x-text="status === 'loading' ? 'Sending...' : 'Submit'"></span>
</button>
</form>
</div>

  • x-data: Initializes the component’s state variables (inputs, status, error states) and custom logic methods (like submitForm and resetForm).
  • x-model: Enables two-way data binding. As the user types in the input fields, Alpine automatically syncs the value to your javascript x-data object properties.
  • x-on:submit.prevent: Attaches an event listener to the form’s submit action. The .prevent modifier automatically stops the browser’s default reload event, allowing Alpine’s submitForm() method to run in the background.
  • x-if: Conditionally renders UI chunks (like success or error cards) based on the component’s current status variable.
  • x-text: Dynamically binds the inner text of an HTML tag to state values, such as displaying the custom error message returned from the API.