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.
AI Prompt (Cursor, Claude, v0, ChatGPT)
Section titled “AI Prompt (Cursor, Claude, v0, ChatGPT)”If you are building with an AI assistant (such as Cursor, Claude, v0, Windsurf, or ChatGPT), copy and paste this prompt to generate an Alpine.js form:
Create a responsive HTML contact form using Alpine.js for interactive state.
Submission Endpoint:https://api.lucidforms.co/f/{your-form-id}
Requirements:- Use Alpine x-data with reactive fields: Name, Email, and Message- Include an invisible honeypot trap field (_honeypot) for spam bot protection- Use @submit.prevent with fetch() to send JSON to the submission endpoint asynchronously- Include 'Content-Type: application/json' and 'Accept: application/json' headers- Manage UI states: idle, loading (disable button), success banner, and error alert- Reset form data upon successful submission- Style with modern, responsive Tailwind CSS classesCode Example
Section titled “Code Example”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) -->
<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>Key Alpine Directives Explained
Section titled “Key Alpine Directives Explained”x-data: Initializes the component’s state variables (inputs, status, error states) and custom logic methods (likesubmitFormandresetForm).x-model: Enables two-way data binding. As the user types in the input fields, Alpine automatically syncs the value to your javascriptx-dataobject properties.x-on:submit.prevent: Attaches an event listener to the form’s submit action. The.preventmodifier automatically stops the browser’s default reload event, allowing Alpine’ssubmitForm()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.

