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 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.liveonly when you need real-time; the defaultwire: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?