Crafting Dynamic Forms with Laravel 12 + Livewire: Zero JavaScript Required

Author

Kritim Yantra

Jun 06, 2025

Crafting Dynamic Forms with Laravel 12 + Livewire: Zero JavaScript Required

Dynamic forms—where users can add/remove fields on the fly—are essential for surveys, multi-step processes, or complex data entry. Traditionally, this required intricate JavaScript. But with Laravel 12 and Livewire, you can build reactive, dynamic forms entirely in PHP. No Alpine.js, no Vue, no JS fatigue. Let’s create one together.


Why Livewire?

  • PHP-centric reactivity: Handle frontend logic in Laravel classes.
  • Zero JS boilerplate: Livewire’s DOM-diffing handles UI updates.
  • Seamless Laravel integration: Leverage Laravel’s validation, sessions, and components.

Step 1: Setup & Installation

  1. Create a Laravel 12 project:
    laravel new dynamic-forms-demo
    
  2. Install Livewire:
    composer require livewire/livewire
    

Step 2: Create a Livewire Component

Generate a ContactForm component:

php artisan make:livewire ContactForm

This creates:

  • app/Livewire/ContactForm.php
  • resources/views/livewire/contact-form.blade.php

Step 3: Build the Dynamic Form Logic

Goal: Let users add/remove multiple email fields.

Edit app/Livewire/ContactForm.php

<?php

namespace App\Livewire;

use Livewire\Component;

class ContactForm extends Component
{
    public $emails = ['']; // Start with one empty email field

    // Add a new empty email field
    public function addEmail()
    {
        $this->emails[] = '';
    }

    // Remove an email field by index
    public function removeEmail($index)
    {
        unset($this->emails[$index]);
        $this->emails = array_values($this->emails); // Reindex array
    }

    // Submit the form
    public function submit()
    {
        // Validate emails (simplified example)
        $validated = $this->validate([
            'emails.*' => 'required|email',
        ]);

        // Process data (e.g., save to database)
        // session()->flash('message', 'Emails saved!');
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}

Step 4: Design the Blade View

Edit resources/views/livewire/contact-form.blade.php:

<div class="max-w-2xl mx-auto p-6 bg-white shadow-lg rounded-lg">
    <form wire:submit.prevent="submit">
        <h2 class="text-2xl font-bold mb-6">Contact Emails</h2>
        
        <!-- Dynamic Email Fields -->
        @foreach ($emails as $index => $email)
            <div class="flex gap-3 mb-4" wire:key="email-{{ $index }}">
                <input 
                    type="email"
                    wire:model="emails.{{ $index }}"
                    placeholder="user@example.com"
                    class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500"
                >
                @if ($index > 0)
                    <button 
                        type="button"
                        wire:click="removeEmail({{ $index }})"
                        class="px-4 bg-red-500 text-white rounded-lg hover:bg-red-600"
                    >
                        Remove
                    </button>
                @endif
            </div>
        @endforeach

        <!-- Add Email Button -->
        <button 
            type="button"
            wire:click="addEmail"
            class="mb-6 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
        >
            + Add Another Email
        </button>

        <!-- Submit Button -->
        <button 
            type="submit"
            class="w-full py-3 bg-green-600 text-white rounded-lg hover:bg-green-700"
        >
            Save Emails
        </button>
    </form>
</div>

Step 5: Add the Component to a Route

Edit routes/web.php:

use App\Livewire\ContactForm;

Route::get('/contact', ContactForm::class);

How It Works

  1. wire:model: Binds input values to Livewire’s PHP properties (synced automatically).
  2. wire:click: Calls PHP methods like addEmail()/removeEmail() when clicked.
  3. wire:submit.prevent: Prevents default form submission; uses submit() method instead.
  4. Reactivity: Livewire re-renders the HTML when $emails changes, updating the UI.

Bonus: Validation & Feedback

Enhance submit() with real-time validation:

public function submit()
{
    $this->validate([
        'emails.*' => 'required|email',
    ], [
        'emails.*.required' => 'Each email field is required.',
        'emails.*.email' => 'Enter a valid email address.',
    ]);

    // Save data...
    session()->flash('success', 'Emails saved successfully!');
}

Display errors in Blade:

@error('emails.' . $index)
    <p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror

Conclusion

With Laravel 12 + Livewire, you’ve built a dynamic form that:
✅ Adds/removes fields reactively,
✅ Validates data in real-time,
✅ Requires zero custom JavaScript.

This is the power of Livewire: complex frontend interactions, written entirely in Laravel. No more context-switching between PHP and JS. Focus on your app’s logic—not the glue code.

Embrace the simplicity. Happy coding! 🚀

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts