Docs menu

Learning Logs · · 3 min read

Learning Livewire: interactive Laravel without JavaScript

Livewire 3 tutorial: one-command install, your first counter component, live search with wire:model.live, and no-reload validation — the same philosophy as this site's HTMX.

Livewire 3LaravelPHP

Livewire makes Laravel pages interactive — without writing JavaScript and without building an API. Your PHP component re-renders on the server every time the user interacts, similar in philosophy to the HTMX approach this site uses, but fully integrated with Laravel.

1. Installation

Prerequisite: a running Laravel project (see the Laravel tutorial). Then:

composer require livewire/livewire

Done. Livewire 3 injects its assets automatically — no manual @livewireStyles/@livewireScripts needed as long as your layout has a normal <html> tag.

2. First component: a counter

php artisan make:livewire counter

That command creates two files:

// app/Livewire/Counter.php
namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public int $count = 0;          // public property = state

    public function increment()     // public method = action from the browser
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}
{{-- resources/views/livewire/counter.blade.php --}}
<div>
    <h2>{{ $count }}</h2>
    <button wire:click="increment">+1</button>
</div>

Drop it into any page:

<livewire:counter />

Click the button: Livewire sends a request to the server, runs increment(), re-renders the component, and swaps its HTML in the browser. You didn’t write a single line of JavaScript.

3. Real-time forms with wire:model

// app/Livewire/ProductSearch.php
class ProductSearch extends Component
{
    public string $query = '';

    public function render()
    {
        return view('livewire.product-search', [
            'products' => Product::where('name', 'like', "%{$this->query}%")->take(10)->get(),
        ]);
    }
}
<div>
    <input type="text" wire:model.live.debounce.300ms="query" placeholder="Search products..." />
    <ul>
        @foreach ($products as $p)
            <li>{{ $p->name }}</li>
        @endforeach
    </ul>
</div>

wire:model.live.debounce.300ms = every keystroke (with a 300ms pause) triggers a re-render — live search with no API endpoint, no fetch, no state management.

4. Validation right in the component

public function save()
{
    $this->validate([
        'name'  => 'required|min:3',
        'price' => 'required|integer|min:0',
    ]);
    Product::create(['name' => $this->name, 'price' => $this->price]);
    $this->reset();
    session()->flash('success', 'Product saved.');
}
@error('name') <span class="error">{{ $message }}</span> @enderror

Error messages appear under the input without a reload — the same pattern as this site’s HTMX contact form, in its Laravel incarnation.

Tips from experience

  • A Livewire component’s root must be a single wrapping <div> — two root elements is the classic source of bugs.
  • Use wire:model.live only when you need real-time; the default wire:model (update on submit) is far cheaper in requests.
  • Livewire shines for admin dashboards and CRUD forms; for public pages that need SEO + maximum speed, plain Blade rendering is still lighter.

Want something like this built for your business?