Laravel Livewire 3 — Complete Guide (2026)
I want to address the skepticism directly before anything else.
"Livewire is a shortcut for developers who don't want to learn JavaScript." I have heard this in every tech forum for three years. It misunderstands what Livewire actually is — and it was always wrong, but the V3 rewrite made it obviously wrong.
Laravel Livewire is a full-stack component framework for Laravel that lets you use PHP and Blade to make interfaces that react and change without a separate JavaScript framework. There is no need to keep an API layer between the front end and the back end. Livewire takes care of the interactivity by managing a thin JavaScript layer that talks to your Laravel app through AJAX requests behind the scenes. Laravel
The entire Livewire core was rewritten for V3. The new core relies on Alpine more, using its Morph, History, and other plugins under the hood, which means Livewire has better diffing, features can be built faster, and there's less duplication between Livewire and Alpine. Medium
This guide covers everything from installation to production — with every major Livewire 3 feature explained with real code.
Installation
bash
composer require livewire/livewire
Add to your Blade layout:
html
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body>
{{ $slot }}
@livewireScripts
</body>
</html>
That is it. No build step. No npm install for Livewire itself. It ships its own JavaScript.
Your First Component
Create the Component
bash
php artisan make:livewire PostForm
This creates two files:
app/Livewire/PostForm.php— the PHP classresources/views/livewire/post-form.blade.php— the Blade view
The PHP Class
php
// app/Livewire/PostForm.php
namespace App\Livewire;
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Rule;
class PostForm extends Component
{
#[Rule('required|string|min:3|max:255')]
public string $title = '';
#[Rule('required|string|min:10')]
public string $body = '';
#[Rule('required|in:draft,published')]
public string $status = 'draft';
public function save(): void
{
$this->validate();
Post::create([
'title' => $this->title,
'body' => $this->body,
'status' => $this->status,
'user_id' => auth()->id(),
]);
session()->flash('message', 'Post created successfully.');
$this->redirect('/posts');
}
public function render()
{
return view('livewire.post-form');
}
}
The Blade View
html
<!-- resources/views/livewire/post-form.blade.php -->
<div>
@if (session('message'))
<div class="alert alert-success">{{ session('message') }}</div>
@endif
<form wire:submit="save">
<div>
<label>Title</label>
<input type="text" wire:model="title" />
@error('title') <span>{{ $message }}</span> @enderror
</div>
<div>
<label>Body</label>
<textarea wire:model="body"></textarea>
@error('body') <span>{{ $message }}</span> @enderror
</div>
<div>
<label>Status</label>
<select wire:model="status">
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
</div>
<button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Save Post</span>
<span wire:loading>Saving...</span>
</button>
</form>
</div>
Use in a Blade Page
html
<!-- resources/views/posts/create.blade.php -->
<x-layouts.app>
<livewire:post-form />
</x-layouts.app>
No controller needed for the form logic. No API endpoint. No JavaScript.
wire:model — Two-Way Data Binding
wire:model binds an input to a PHP property. Type in the input — the PHP property updates. PHP updates the property — the input reflects it.
html
<!-- Instant binding — fires on every keystroke --> <input wire:model="search" type="text" placeholder="Search..." /> <!-- Debounced — waits 500ms after last keystroke --> <input wire:model.debounce.500ms="search" type="text" /> <!-- Lazy — only updates on blur (input loses focus) --> <input wire:model.lazy="email" type="email" /> <!-- Live — alias for instant (explicit) --> <input wire:model.live="username" type="text" />
Live Search Example
php
// app/Livewire/PostSearch.php
class PostSearch extends Component
{
public string $search = '';
public function render()
{
$posts = Post::query()
->when($this->search, fn($q) =>
$q->where('title', 'like', "%{$this->search}%")
->orWhere('body', 'like', "%{$this->search}%")
)
->latest()
->paginate(10);
return view('livewire.post-search', ['posts' => $posts]);
}
}
html
<!-- resources/views/livewire/post-search.blade.php -->
<div>
<input
wire:model.debounce.300ms="search"
type="text"
placeholder="Search posts..."
/>
<div>
@forelse ($posts as $post)
<div>
<h3>{{ $post->title }}</h3>
<p>{{ $post->excerpt }}</p>
</div>
@empty
<p>No posts found for "{{ $search }}".</p>
@endforelse
</div>
{{ $posts->links() }}
</div>
Live search with pagination. No JavaScript. No API endpoint. No debounce library.
Actions — Calling PHP Methods from the Browser
php
// app/Livewire/PostList.php
class PostList extends Component
{
public function delete(int $postId): void
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post);
$post->delete();
$this->dispatch('post-deleted');
}
public function toggleFeatured(Post $post): void
{
// Laravel automatically resolves the model from the ID
$post->update(['featured' => !$post->featured]);
}
public function render()
{
return view('livewire.post-list', [
'posts' => Post::latest()->paginate(20),
]);
}
}
html
<!-- resources/views/livewire/post-list.blade.php -->
<div>
@foreach ($posts as $post)
<div>
<h3>{{ $post->title }}</h3>
<!-- Call PHP method directly -->
<button wire:click="toggleFeatured({{ $post->id }})">
{{ $post->featured ? 'Unfeature' : 'Feature' }}
</button>
<!-- With confirmation dialog -->
<button
wire:click="delete({{ $post->id }})"
wire:confirm="Are you sure you want to delete this post?"
>
Delete
</button>
</div>
@endforeach
</div>
Lifecycle Hooks
php
class UserProfile extends Component
{
public int $userId;
public string $name = '';
public string $email = '';
// Runs once when component is first created
public function mount(int $userId): void
{
$this->userId = $userId;
$user = User::findOrFail($userId);
$this->name = $user->name;
$this->email = $user->email;
}
// Runs every time a property updates
public function updated(string $property): void
{
\Log::info("Property updated: {$property}");
}
// Runs when SPECIFIC property updates
public function updatedName(string $value): void
{
$this->name = str($value)->title()->value();
}
// Runs before render
public function rendering(): void
{
// Prepare data before the view renders
}
public function render()
{
return view('livewire.user-profile');
}
}
wire:navigate — SPA Without a JavaScript Framework
Livewire 3 offers an SPA experience for your application using wire:navigate. Medium
This is the feature that changed how I think about Livewire. Add one attribute to links and your entire application navigates like a React SPA — without writing a single line of JavaScript routing code.
html
<!-- Regular link — full page reload --> <a href="/dashboard">Dashboard</a> <!-- Livewire navigate — instant, SPA-style --> <a wire:navigate href="/dashboard">Dashboard</a> <a wire:navigate href="/posts">Posts</a> <a wire:navigate href="/settings">Settings</a>
What wire:navigate does:
- Intercepts the click
- Fetches the new page via AJAX
- Swaps the body content
- Updates the browser URL and history
- Preserves scroll position on back/forward
- Prefetches links on hover
html
<!-- Prefetch on hover — instant feel --> <a wire:navigate.hover href="/posts">Posts</a> <!-- Keep component alive between navigations --> <!-- Useful for audio players, sidebars, persistent state --> <livewire:music-player persist />
Volt — Single-File Components
Livewire V3 includes hot reloading without a build step. Just save a file in your editor and instantly see those changes in your browser without breaking your components' state. Medium
Volt takes this further — one file for both the PHP logic and the Blade view.
php
<!-- resources/views/livewire/counter.blade.php -->
<?php
use Livewire\Volt\Component;
new class extends Component
{
public int $count = 0;
public function increment(): void
{
$this->count++;
}
public function decrement(): void
{
$this->count = max(0, $this->count - 1);
}
}; ?>
<div>
<button wire:click="decrement">-</button>
<span>{{ $count }}</span>
<button wire:click="increment">+</button>
</div>
No separate PHP class file. Logic and view in one place. For simple components this is significantly cleaner.
Volt Functional API
php
<!-- resources/views/livewire/search.blade.php -->
<?php
use function Livewire\Volt\{state, computed};
use App\Models\Post;
state(['search' => '']);
$results = computed(function () {
return Post::where('title', 'like', "%{$this->search}%")
->limit(5)
->get();
});
?>
<div>
<input wire:model.debounce.300ms="search" type="text" />
@foreach ($this->results as $post)
<div>{{ $post->title }}</div>
@endforeach
</div>
Lazy Loading — Instant Page Loads
Heavy components slow down your initial page load. Lazy loading renders a placeholder first and loads the component after.
html
<!-- Load after page renders — no initial delay -->
<livewire:analytics-chart lazy />
<livewire:recent-orders lazy />
<livewire:notification-feed lazy />
<!-- Custom placeholder while loading -->
<livewire:heavy-report lazy>
<x-slot:placeholder>
<div class="animate-pulse bg-gray-200 h-48 rounded"></div>
</x-slot:placeholder>
</livewire:heavy-report>
php
// app/Livewire/AnalyticsChart.php
class AnalyticsChart extends Component
{
public function placeholder()
{
return <<<'HTML'
<div class="animate-pulse bg-gray-100 h-64 rounded-lg flex items-center justify-center">
<span class="text-gray-400">Loading chart...</span>
</div>
HTML;
}
public function render()
{
// This only runs after the placeholder has been shown
$data = $this->buildExpensiveChartData();
return view('livewire.analytics-chart', ['data' => $data]);
}
}
Polling — Live Data Without WebSockets
html
<!-- Refresh every 5 seconds -->
<div wire:poll.5s>
<p>Active users: {{ $activeUsers }}</p>
<p>Orders today: {{ $ordersToday }}</p>
</div>
<!-- Refresh every 30 seconds — less aggressive -->
<div wire:poll.30s>
<p>Server status: {{ $serverStatus }}</p>
</div>
<!-- Only poll when the browser tab is visible -->
<div wire:poll.5s.visible>
Live price: ₹{{ $price }}
</div>
php
class LiveDashboard extends Component
{
public int $activeUsers = 0;
public int $ordersToday = 0;
public string $serverStatus = 'healthy';
public function render()
{
// Called every poll interval
$this->activeUsers = Cache::get('active_users_count', 0);
$this->ordersToday = Order::whereDate('created_at', today())->count();
return view('livewire.live-dashboard');
}
}
For most "live" dashboard use cases — server stats, order counts, user activity — polling is simpler than WebSockets and reliable enough.
Events — Component Communication
php
// app/Livewire/PostForm.php
class PostForm extends Component
{
public function save(): void
{
$post = Post::create($this->validate());
// Dispatch event to other components
$this->dispatch('post-created', postId: $post->id);
$this->reset(['title', 'body']);
}
}
php
// app/Livewire/PostList.php
use Livewire\Attributes\On;
class PostList extends Component
{
// Listen for the event
#[On('post-created')]
public function handlePostCreated(int $postId): void
{
// Component re-renders automatically
// No manual refresh needed
session()->flash('message', 'New post added to the list!');
}
public function render()
{
return view('livewire.post-list', [
'posts' => Post::latest()->paginate(20),
]);
}
}
html
<!-- Dispatch browser events to Alpine.js or vanilla JS -->
<button wire:click="$dispatch('open-modal', { id: 'confirm-delete' })">
Delete
</button>
File Uploads
Livewire handles file uploads server-side with temporary upload URLs, validation, and S3 support built in. The input element is essentially the only frontend code needed. Laravel News
php
// app/Livewire/AvatarUpload.php
use Livewire\WithFileUploads;
use Livewire\Attributes\Rule;
class AvatarUpload extends Component
{
use WithFileUploads;
#[Rule('image|max:2048')] // max 2MB
public $avatar;
public function save(): void
{
$this->validate();
$path = $this->avatar->store('avatars', 's3');
auth()->user()->update(['avatar_path' => $path]);
$this->dispatch('avatar-updated');
session()->flash('message', 'Avatar updated successfully.');
}
public function render()
{
return view('livewire.avatar-upload');
}
}
html
<!-- resources/views/livewire/avatar-upload.blade.php -->
<div>
<form wire:submit="save">
<input type="file" wire:model="avatar" />
<!-- Show upload progress -->
<div wire:loading wire:target="avatar">
Uploading...
</div>
<!-- Preview before saving -->
@if ($avatar)
<img src="{{ $avatar->temporaryUrl() }}" width="150">
@endif
@error('avatar')
<span class="text-red-500">{{ $message }}</span>
@enderror
<button type="submit">Save Avatar</button>
</form>
</div>
Temporary URLs work for previewing before the upload is confirmed. S3 uploads, progress indicators, validation — no JavaScript needed.
Testing Livewire Components
Livewire provides first-class testing support. Test UI interactions in PHP without a browser or JavaScript test runner. Practical testing patterns prevent regressions in forms and state transitions. Laravel News
php
// tests/Feature/Livewire/PostFormTest.php
use App\Livewire\PostForm;
use App\Models\Post;
use App\Models\User;
use Livewire\Livewire;
test('renders post form', function () {
$user = User::factory()->create();
Livewire::actingAs($user)
->test(PostForm::class)
->assertStatus(200)
->assertSee('Title')
->assertSee('Save Post');
});
test('creates post with valid data', function () {
$user = User::factory()->create();
Livewire::actingAs($user)
->test(PostForm::class)
->set('title', 'My First Post')
->set('body', 'This is the post content with enough text.')
->set('status', 'published')
->call('save')
->assertHasNoErrors()
->assertRedirect('/posts');
expect(Post::where('title', 'My First Post')->exists())->toBeTrue();
});
test('shows validation errors for empty title', function () {
$user = User::factory()->create();
Livewire::actingAs($user)
->test(PostForm::class)
->set('title', '')
->call('save')
->assertHasErrors(['title' => 'required']);
});
test('search filters posts correctly', function () {
Post::factory()->create(['title' => 'Laravel is great']);
Post::factory()->create(['title' => 'Vue is also great']);
Livewire::test(PostSearch::class)
->set('search', 'Laravel')
->assertSee('Laravel is great')
->assertDontSee('Vue is also great');
});
test('dispatches event after save', function () {
$user = User::factory()->create();
$title = 'Test Post ' . uniqid();
Livewire::actingAs($user)
->test(PostForm::class)
->set('title', $title)
->set('body', 'Content with enough characters here.')
->call('save')
->assertDispatched('post-created');
});
Livewire vs React/Vue — Honest Answer
Pick Livewire if you are building an admin panel, SaaS dashboard, or anything where the UI is form-heavy and data-driven. Livewire is faster to ship for this kind of work because you are not managing a separate frontend build or serializing everything through props. You also get strong SEO defaults since content renders server-side first. Impact Techlab
Most Laravel developers now use AI-assisted development tools like GitHub Copilot, Cursor, and Claude Code. These tools work better on PHP and Blade codebases than they do on complex TypeScript SPA architectures. Keeping the stack PHP-native makes it easier for both developers and AI tools to work together. Laravel
Use Livewire for:
- Admin panels and dashboards
- SaaS application UIs
- Forms with real-time validation
- Data tables with filtering and sorting
- Any UI that is form-heavy and data-driven
- SEO-important pages that need reactivity
Use React/Vue/Inertia for:
- Highly interactive UIs with complex client-side state
- Mobile-first progressive web apps
- Real-time collaborative features
- Teams with strong JavaScript backgrounds
- Applications where the frontend is genuinely complex
Livewire 4 — What Changed in January 2026
Livewire 4 shipped in January 2026. The Islands feature specifically removes one of Livewire's older weaknesses — the performance ceiling you would hit with complex dashboards. Filament v5 already runs on Livewire v4, so the ecosystem stays consistent. Impact Techlab
Upgrade from V3 to V4:
bash
composer require livewire/livewire:^4.0 php artisan livewire:upgrade
The upgrade command handles most breaking changes automatically. Key changes:
- Islands architecture for isolated reactive sections
- Improved diffing algorithm
- Better Alpine.js integration
- Teleport improvements
Production Checklist
bash
# Session — use Redis, not file driver SESSION_DRIVER=redis SESSION_LIFETIME=120 SESSION_ENCRYPT=true # Cache — Redis for Livewire component caching CACHE_DRIVER=redis # Assets — build and version for production npm run build php artisan livewire:publish --assets
php
// config/livewire.php — production settings
return [
'class_namespace' => 'App\\Livewire',
'view_path' => resource_path('views/livewire'),
// Inject assets automatically
'inject_assets' => true,
// Use full page component rendering
'layout' => 'layouts.app',
// Temporary upload disk
'temporary_file_upload' => [
'disk' => 's3',
'rules' => ['required', 'file', 'max:12288'],
'directory' => 'tmp',
'middleware' => 'throttle:60,1',
'preview_mimes' => ['png', 'gif', 'bmp', 'svg', 'wav', 'mp4', ...],
],
];
Wrapping Up
The case for Livewire has gotten stronger in the last few years, not weaker. Now that Livewire v4 is the current stable release in 2026, the framework has grown up enough that it cannot be called a niche convenience tool anymore. Laravel
Livewire 3 introduced wire:navigate for SPA navigation, Volt for single-file components, lazy loading, and proper Alpine.js integration. Livewire 4 built on that with Islands and better performance for complex UIs.
For Laravel developers building data-driven applications — admin panels, SaaS dashboards, internal tools — Livewire is consistently faster to build and easier to maintain than a full SPA architecture. Not because it is a shortcut, but because it eliminates an entire layer of complexity that the application did not need in the first place.
Tushar Modi — Full Stack Developer, Jaipur tusharmodi.in