Form Handling
Gale makes form handling effortless by combining Alpine's reactive data binding with automatic state serialization. No JavaScript form handling code required - just bind your inputs and submit.
Basic Form Structure
A Gale form uses Alpine's x-model for two-way binding and HTTP magics
for submission. The x-message directive displays validation errors.
<form x-data="{ name: '', email: '' }"
@submit.prevent="$postx('/contact')">
<div>
<label>Name</label>
<input type="text" x-model="name">
<span x-message="name" class="error"></span>
</div>
<div>
<label>Email</label>
<input type="email" x-model="email">
<span x-message="email" class="error"></span>
</div>
<button type="submit">Submit</button>
</form>
<form x-data="{ name: '', email: '' }"
@submit.prevent="$postx('/contact')">
<div>
<label>Name</label>
<input type="text" x-model="name">
<span x-message="name" class="error"></span>
</div>
<div>
<label>Email</label>
<input type="email" x-model="email">
<span x-message="email" class="error"></span>
</div>
<button type="submit">Submit</button>
</form>
// Controller
public function contact(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
// Process the form...
Contact::create($validated);
return gale()
->state(['name' => '', 'email' => ''])
->messages(['_success' => 'Message sent!']);
}
// Controller
public function contact(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
// Process the form...
Contact::create($validated);
return gale()
->state(['name' => '', 'email' => ''])
->messages(['_success' => 'Message sent!']);
}
The x-message Directive
The x-message directive automatically displays messages from the server
for a specific field. It's commonly used for validation errors but works for any messages.
<!-- Basic usage -->
<span x-message="email"></span>
<!-- With styling -->
<span x-message="email" class="text-red-500 text-sm"></span>
<!-- For global messages -->
<div x-message="_success" class="alert-success"></div>
<div x-message="_error" class="alert-error"></div>
<!-- Basic usage --> <span x-message="email"></span> <!-- With styling --> <span x-message="email" class="text-red-500 text-sm"></span> <!-- For global messages --> <div x-message="_success" class="alert-success"></div> <div x-message="_error" class="alert-error"></div>
Sending Messages from Backend
// Field-specific messages
gale()->messages([
'email' => 'This email is already registered.',
'username' => 'Username must be at least 3 characters.',
]);
// Global success message
gale()->messages(['_success' => 'Profile updated!']);
// Global error message
gale()->messages(['_error' => 'Something went wrong.']);
// Clear all messages
gale()->clearMessages();
// Field-specific messages
gale()->messages([
'email' => 'This email is already registered.',
'username' => 'Username must be at least 3 characters.',
]);
// Global success message
gale()->messages(['_success' => 'Profile updated!']);
// Global error message
gale()->messages(['_error' => 'Something went wrong.']);
// Clear all messages
gale()->clearMessages();
Form Loading States
Use the x-loading directive to show/hide elements during requests:
<form x-data="{ name: '', email: '' }"
@submit.prevent="$postx('/submit')">
<!-- Form fields... -->
<button type="submit" x-loading.disabled>
<span x-loading.remove>Submit</span>
<span x-loading>Submitting...</span>
</button>
</form>
<form x-data="{ name: '', email: '' }"
@submit.prevent="$postx('/submit')">
<!-- Form fields... -->
<button type="submit" x-loading.disabled>
<span x-loading.remove>Submit</span>
<span x-loading>Submitting...</span>
</button>
</form>
Handling Different Input Types
Text Inputs
<div x-data="{ username: '', bio: '' }">
<!-- Text input -->
<input type="text" x-model="username">
<!-- Textarea -->
<textarea x-model="bio"></textarea>
</div>
<div x-data="{ username: '', bio: '' }">
<!-- Text input -->
<input type="text" x-model="username">
<!-- Textarea -->
<textarea x-model="bio"></textarea>
</div>
Checkboxes
<div x-data="{ newsletter: false, notifications: [] }">
<!-- Single checkbox (boolean) -->
<label>
<input type="checkbox" x-model="newsletter">
Subscribe to newsletter
</label>
<!-- Multiple checkboxes (array) -->
<label>
<input type="checkbox" value="email" x-model="notifications">
Email notifications
</label>
<label>
<input type="checkbox" value="sms" x-model="notifications">
SMS notifications
</label>
</div>
<!-- Server receives: { newsletter: true, notifications: ["email", "sms"] } -->
<div x-data="{ newsletter: false, notifications: [] }">
<!-- Single checkbox (boolean) -->
<label>
<input type="checkbox" x-model="newsletter">
Subscribe to newsletter
</label>
<!-- Multiple checkboxes (array) -->
<label>
<input type="checkbox" value="email" x-model="notifications">
Email notifications
</label>
<label>
<input type="checkbox" value="sms" x-model="notifications">
SMS notifications
</label>
</div>
<!-- Server receives: { newsletter: true, notifications: ["email", "sms"] } -->
Radio Buttons
<div x-data="{ plan: 'basic' }">
<label>
<input type="radio" value="basic" x-model="plan">
Basic Plan
</label>
<label>
<input type="radio" value="pro" x-model="plan">
Pro Plan
</label>
<label>
<input type="radio" value="enterprise" x-model="plan">
Enterprise Plan
</label>
</div>
<!-- Server receives: { plan: "pro" } -->
<div x-data="{ plan: 'basic' }">
<label>
<input type="radio" value="basic" x-model="plan">
Basic Plan
</label>
<label>
<input type="radio" value="pro" x-model="plan">
Pro Plan
</label>
<label>
<input type="radio" value="enterprise" x-model="plan">
Enterprise Plan
</label>
</div>
<!-- Server receives: { plan: "pro" } -->
Select Dropdowns
<div x-data="{ country: '', colors: [] }">
<!-- Single select -->
<select x-model="country">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
<!-- Multiple select -->
<select x-model="colors" multiple>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
</div>
<div x-data="{ country: '', colors: [] }">
<!-- Single select -->
<select x-model="country">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
<!-- Multiple select -->
<select x-model="colors" multiple>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
</div>
Nested Form Data
For complex forms, group related fields in nested objects:
<form x-data="{
user: {
name: '',
email: ''
},
address: {
street: '',
city: '',
zip: ''
}
}" @submit.prevent="$postx('/register')">
<h3>Personal Info</h3>
<input type="text" x-model="user.name" placeholder="Name">
<span x-message="user.name"></span>
<input type="email" x-model="user.email" placeholder="Email">
<span x-message="user.email"></span>
<h3>Address</h3>
<input type="text" x-model="address.street" placeholder="Street">
<input type="text" x-model="address.city" placeholder="City">
<input type="text" x-model="address.zip" placeholder="ZIP">
<button type="submit">Register</button>
</form>
<form x-data="{
user: {
name: '',
email: ''
},
address: {
street: '',
city: '',
zip: ''
}
}" @submit.prevent="$postx('/register')">
<h3>Personal Info</h3>
<input type="text" x-model="user.name" placeholder="Name">
<span x-message="user.name"></span>
<input type="email" x-model="user.email" placeholder="Email">
<span x-message="user.email"></span>
<h3>Address</h3>
<input type="text" x-model="address.street" placeholder="Street">
<input type="text" x-model="address.city" placeholder="City">
<input type="text" x-model="address.zip" placeholder="ZIP">
<button type="submit">Register</button>
</form>
// Controller - validate nested data
$validated = $request->validate([
'user.name' => 'required|string',
'user.email' => 'required|email',
'address.street' => 'required|string',
'address.city' => 'required|string',
'address.zip' => 'required|string',
]);
// Controller - validate nested data
$validated = $request->validate([
'user.name' => 'required|string',
'user.email' => 'required|email',
'address.street' => 'required|string',
'address.city' => 'required|string',
'address.zip' => 'required|string',
]);
Dynamic Form Fields
Handle dynamic form fields like repeatable rows:
<form x-data="{
items: [{ name: '', quantity: 1 }],
addItem() {
this.items.push({ name: '', quantity: 1 });
},
removeItem(index) {
this.items.splice(index, 1);
}
}" @submit.prevent="$postx('/order')">
<template x-for="(item, index) in items" :key="index">
<div class="item-row">
<input type="text" x-model="item.name" placeholder="Item name">
<input type="number" x-model.number="item.quantity" min="1">
<button type="button" @click="removeItem(index)">Remove</button>
</div>
</template>
<button type="button" @click="addItem()">Add Item</button>
<button type="submit">Submit Order</button>
</form>
<form x-data="{
items: [{ name: '', quantity: 1 }],
addItem() {
this.items.push({ name: '', quantity: 1 });
},
removeItem(index) {
this.items.splice(index, 1);
}
}" @submit.prevent="$postx('/order')">
<template x-for="(item, index) in items" :key="index">
<div class="item-row">
<input type="text" x-model="item.name" placeholder="Item name">
<input type="number" x-model.number="item.quantity" min="1">
<button type="button" @click="removeItem(index)">Remove</button>
</div>
</template>
<button type="button" @click="addItem()">Add Item</button>
<button type="submit">Submit Order</button>
</form>
Resetting Forms
Reset form values from the server after successful submission:
public function submitContact(Request $request)
{
$validated = $request->validate([...]);
Contact::create($validated);
// Reset form fields
return gale()
->state([
'name' => '',
'email' => '',
'message' => '',
])
->messages(['_success' => 'Thank you for your message!']);
}
public function submitContact(Request $request)
{
$validated = $request->validate([...]);
Contact::create($validated);
// Reset form fields
return gale()
->state([
'name' => '',
'email' => '',
'message' => '',
])
->messages(['_success' => 'Thank you for your message!']);
}
Complete Form Example
<form
x-data="{
name: '',
email: '',
plan: 'basic',
features: [],
newsletter: false,
_showSuccess: false
}"
@submit.prevent="$postx('/register', {
include: ['name', 'email', 'plan', 'features', 'newsletter']
})"
class="space-y-4">
<!-- Success message -->
<div x-show="_showSuccess" x-message="_success" class="alert-success"></div>
<!-- Name field -->
<div>
<label>Full Name</label>
<input type="text" x-model="name">
<span x-message="name" class="error"></span>
</div>
<!-- Email field -->
<div>
<label>Email</label>
<input type="email" x-model="email">
<span x-message="email" class="error"></span>
</div>
<!-- Plan selection -->
<div>
<label>Plan</label>
<select x-model="plan">
<option value="basic">Basic</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</select>
</div>
<!-- Features checkboxes -->
<div>
<label>
<input type="checkbox" value="api" x-model="features"> API Access
</label>
<label>
<input type="checkbox" value="support" x-model="features"> Priority Support
</label>
</div>
<!-- Newsletter checkbox -->
<label>
<input type="checkbox" x-model="newsletter">
Subscribe to newsletter
</label>
<!-- Submit button with loading state -->
<button type="submit" x-loading.disabled>
<span x-loading.remove>Register</span>
<span x-loading>Processing...</span>
</button>
</form>
<form
x-data="{
name: '',
email: '',
plan: 'basic',
features: [],
newsletter: false,
_showSuccess: false
}"
@submit.prevent="$postx('/register', {
include: ['name', 'email', 'plan', 'features', 'newsletter']
})"
class="space-y-4">
<!-- Success message -->
<div x-show="_showSuccess" x-message="_success" class="alert-success"></div>
<!-- Name field -->
<div>
<label>Full Name</label>
<input type="text" x-model="name">
<span x-message="name" class="error"></span>
</div>
<!-- Email field -->
<div>
<label>Email</label>
<input type="email" x-model="email">
<span x-message="email" class="error"></span>
</div>
<!-- Plan selection -->
<div>
<label>Plan</label>
<select x-model="plan">
<option value="basic">Basic</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</select>
</div>
<!-- Features checkboxes -->
<div>
<label>
<input type="checkbox" value="api" x-model="features"> API Access
</label>
<label>
<input type="checkbox" value="support" x-model="features"> Priority Support
</label>
</div>
<!-- Newsletter checkbox -->
<label>
<input type="checkbox" x-model="newsletter">
Subscribe to newsletter
</label>
<!-- Submit button with loading state -->
<button type="submit" x-loading.disabled>
<span x-loading.remove>Register</span>
<span x-loading>Processing...</span>
</button>
</form>