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>
// 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>

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();

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>

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>

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"] } -->

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" } -->

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>

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>
// 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>

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!']);
}

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>

On this page