Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Livewire
https://github.com/livewire/livewire
Admin
A full-stack framework for Laravel that takes the pain out of building dynamic UIs.
Tokens:
214,845
Snippets:
1,927
Trust Score:
7.4
Update:
2 months ago
Context
Skills
Chat
Benchmark
79.6
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Livewire Livewire is a full-stack framework for Laravel that allows you to build dynamic, reactive user interfaces using only PHP. Instead of writing JavaScript frameworks like Vue or React, you write simple PHP classes and Blade templates, and Livewire handles all the complex JavaScript behind the scenes. It renders on the server and uses a morphing algorithm to patch the DOM with updates, "dehydrating" component state into a JS-consumable object that is passed back to the server to be "hydrated" during the next request. Livewire enables you to create modern, reactive applications while staying within the familiar Laravel ecosystem. It provides data binding, real-time validation, form handling, event dispatching, and SPA-like navigation - all without leaving PHP. Components can be used as reusable UI elements or as full pages with routing, layouts, and page titles. ## Installation Install Livewire via Composer and create a layout file for full-page components. ```shell # Install Livewire (v4 beta) composer require livewire/livewire:^4.0@beta # Create a layout file php artisan livewire:layout ``` ## Creating Components Create Livewire components using the Artisan command. Components can be single-file (default), multi-file, or class-based. ```shell # Create a single-file component php artisan make:livewire post.create # Create a page component (in pages namespace) php artisan make:livewire pages::post.create # Create a multi-file component php artisan make:livewire post.create --mfc # Create a class-based component (traditional style) php artisan make:livewire CreatePost --class # Convert between formats php artisan livewire:convert post.create --mfc php artisan livewire:convert post.create --sfc ``` ## Single-File Component Structure Single-file components combine PHP logic and Blade template in one file, providing a clean and intuitive development experience. ```blade <?php // resources/views/components/post/⚡create.blade.php use Livewire\Attributes\Validate; use Livewire\Component; use App\Models\Post; new class extends Component { #[Validate('required|min:5')] public string $title = ''; #[Validate('required|min:10')] public string $content = ''; public function save() { $validated = $this->validate(); Post::create($validated); session()->flash('message', 'Post created successfully!'); return $this->redirect('/posts'); } }; ?> <form wire:submit="save"> <div> <label for="title">Title</label> <input type="text" id="title" wire:model="title"> @error('title') <span class="text-red-500">{{ $message }}</span> @enderror </div> <div> <label for="content">Content</label> <textarea id="content" wire:model="content" rows="5"></textarea> @error('content') <span class="text-red-500">{{ $message }}</span> @enderror </div> <button type="submit">Save Post</button> <span wire:loading>Saving...</span> </form> ``` ## Routing to Components (Full Page Components) Register components as full pages using Route::livewire() with support for route parameters and model binding. ```php // routes/web.php use Illuminate\Support\Facades\Route; // Basic route Route::livewire('/posts/create', 'pages::post.create'); // Route with parameter Route::livewire('/posts/{id}', 'pages::post.show'); // Route with model binding Route::livewire('/posts/{post}', 'pages::post.edit'); // With middleware Route::livewire('/admin/posts', 'pages::admin.posts') ->middleware(['auth', 'admin']); ``` ```php <?php // resources/views/pages/post/⚡show.blade.php use Livewire\Attributes\Title; use Livewire\Attributes\Layout; use Livewire\Component; use App\Models\Post; new #[Title('View Post')] #[Layout('layouts::dashboard')] class extends Component { public Post $post; // Automatically bound from route parameter // mount() is optional when property name matches route parameter }; ?> <div> <h1>{{ $post->title }}</h1> <p>{{ $post->content }}</p> </div> ``` ## Properties and Data Binding Properties store component state and can be bound to form inputs using wire:model. Use #[Validate] for automatic validation. ```php <?php // resources/views/components/⚡user-profile.blade.php use Livewire\Attributes\Validate; use Livewire\Attributes\Locked; use Livewire\Component; new class extends Component { #[Locked] // Prevent client-side tampering public int $userId; #[Validate('required|min:2|max:255')] public string $name = ''; #[Validate('required|email')] public string $email = ''; #[Validate('nullable|url')] public ?string $website = null; public array $preferences = []; public function mount(int $userId) { $this->userId = $userId; $user = User::find($userId); $this->fill($user->only(['name', 'email', 'website'])); $this->preferences = $user->preferences ?? []; } public function save() { $validated = $this->validate(); User::find($this->userId)->update($validated); session()->flash('status', 'Profile updated!'); } public function resetForm() { $this->reset(['name', 'email', 'website']); } }; ?> <form wire:submit="save"> <!-- Default: syncs on form submit --> <input type="text" wire:model="name"> <!-- Live: syncs as user types (with debounce) --> <input type="email" wire:model.live="email"> <!-- Blur: syncs when input loses focus --> <input type="url" wire:model.blur="website"> <!-- Debounce with custom timing --> <input type="text" wire:model.live.debounce.500ms="name"> <button type="submit">Save</button> <button type="button" wire:click="resetForm">Reset</button> </form> ``` ## Actions (Methods) Actions are public methods that can be triggered from the frontend. They support parameters, dependency injection, and various modifiers. ```php <?php // resources/views/components/⚡todo-list.blade.php use Livewire\Attributes\Computed; use Livewire\Attributes\Renderless; use Livewire\Attributes\On; use Livewire\Component; use App\Models\Todo; new class extends Component { public string $newTodo = ''; #[Computed] public function todos() { return auth()->user()->todos()->latest()->get(); } public function addTodo() { $this->validate(['newTodo' => 'required|min:3']); auth()->user()->todos()->create([ 'content' => $this->pull('newTodo'), // Get value and reset ]); unset($this->todos); // Clear computed cache } public function deleteTodo(Todo $todo) // Model binding in actions { $this->authorize('delete', $todo); $todo->delete(); unset($this->todos); } public function toggleComplete(int $todoId) { $todo = Todo::findOrFail($todoId); $this->authorize('update', $todo); $todo->update(['completed' => !$todo->completed]); } #[Renderless] // Skip re-rendering after this action public function logActivity(string $action) { activity()->log($action); } #[On('todo-added')] // Listen for events public function handleTodoAdded() { unset($this->todos); } }; ?> <div> <form wire:submit="addTodo"> <input type="text" wire:model="newTodo" placeholder="New todo..."> <button type="submit">Add</button> </form> <ul> @foreach ($this->todos as $todo) <li wire:key="{{ $todo->id }}"> <input type="checkbox" wire:click="toggleComplete({{ $todo->id }})" @checked($todo->completed) > <span>{{ $todo->content }}</span> <!-- Confirm before action --> <button wire:click="deleteTodo({{ $todo->id }})" wire:confirm="Are you sure you want to delete this todo?" > Delete </button> </li> @endforeach </ul> <!-- Magic actions --> <button wire:click="$refresh">Refresh</button> <button wire:click="$set('newTodo', '')">Clear Input</button> <button wire:click="$toggle('showCompleted')">Toggle View</button> </div> ``` ## Computed Properties Computed properties are memoized methods that cache their results for the duration of a request. They're ideal for expensive operations like database queries. ```php <?php // resources/views/components/⚡dashboard.blade.php use Livewire\Attributes\Computed; use Livewire\Component; use App\Models\Post; use App\Models\User; new class extends Component { public string $search = ''; public string $status = 'all'; // Basic computed property - memoized per request #[Computed] public function posts() { return Post::query() ->when($this->search, fn($q) => $q->where('title', 'like', "%{$this->search}%")) ->when($this->status !== 'all', fn($q) => $q->where('status', $this->status)) ->with('author') ->latest() ->get(); } // Cached across requests (1 hour default) #[Computed(persist: true)] public function popularPosts() { return Post::withCount('views') ->orderByDesc('views_count') ->limit(5) ->get(); } // Cached globally across all component instances #[Computed(cache: true, key: 'site-stats', seconds: 3600)] public function siteStats() { return [ 'users' => User::count(), 'posts' => Post::count(), 'views' => Post::sum('view_count'), ]; } public function updatedSearch() { // Clear computed cache when search changes unset($this->posts); } }; ?> <div> <input type="text" wire:model.live.debounce.300ms="search" placeholder="Search..."> <select wire:model.live="status"> <option value="all">All</option> <option value="published">Published</option> <option value="draft">Draft</option> </select> <div> @foreach ($this->posts as $post) <article wire:key="{{ $post->id }}"> <h2>{{ $post->title }}</h2> <p>By {{ $post->author->name }}</p> </article> @endforeach </div> <aside> <h3>Popular Posts</h3> @foreach ($this->popularPosts as $post) <div>{{ $post->title }} ({{ $post->views_count }} views)</div> @endforeach </aside> <footer> <p>{{ $this->siteStats['users'] }} users, {{ $this->siteStats['posts'] }} posts</p> </footer> </div> ``` ## Form Objects Form objects extract form logic into reusable classes for cleaner components and code reuse. ```php <?php // app/Livewire/Forms/PostForm.php namespace App\Livewire\Forms; use Livewire\Attributes\Validate; use Livewire\Form; use App\Models\Post; class PostForm extends Form { public ?Post $post = null; #[Validate('required|min:5|max:255')] public string $title = ''; #[Validate('required|min:10')] public string $content = ''; #[Validate('nullable|array')] public array $tags = []; public function setPost(Post $post): void { $this->post = $post; $this->title = $post->title; $this->content = $post->content; $this->tags = $post->tags ?? []; } public function store(): Post { $validated = $this->validate(); $post = auth()->user()->posts()->create($validated); $this->reset(); return $post; } public function update(): Post { $validated = $this->validate(); $this->post->update($validated); return $this->post; } } ``` ```php <?php // resources/views/components/post/⚡edit.blade.php use App\Livewire\Forms\PostForm; use Livewire\Component; use App\Models\Post; new class extends Component { public PostForm $form; public function mount(Post $post) { $this->form->setPost($post); } public function save() { $this->form->update(); session()->flash('message', 'Post updated!'); return $this->redirect('/posts'); } }; ?> <form wire:submit="save"> <input type="text" wire:model="form.title"> @error('form.title') <span>{{ $message }}</span> @enderror <textarea wire:model="form.content"></textarea> @error('form.content') <span>{{ $message }}</span> @enderror <button type="submit">Update Post</button> </form> ``` ## Events Events enable communication between components using a pub/sub pattern. Events can be dispatched and listened to across the entire page. ```php <?php // resources/views/components/post/⚡create.blade.php use Livewire\Component; use App\Models\Post; new class extends Component { public string $title = ''; public string $content = ''; public function save() { $post = Post::create([ 'title' => $this->title, 'content' => $this->content, ]); // Dispatch event to all listening components $this->dispatch('post-created', postId: $post->id, title: $post->title); // Dispatch to a specific component $this->dispatch('post-created')->to(Dashboard::class); // Dispatch only to self $this->dispatch('post-saved')->self(); $this->reset(); } }; ?> <form wire:submit="save"> <input type="text" wire:model="title"> <textarea wire:model="content"></textarea> <button type="submit">Create Post</button> </form> ``` ```php <?php // resources/views/components/⚡dashboard.blade.php use Livewire\Attributes\On; use Livewire\Attributes\Computed; use Livewire\Component; new class extends Component { public int $postCount = 0; public function mount() { $this->postCount = Post::count(); } #[On('post-created')] public function handlePostCreated(int $postId, string $title) { $this->postCount++; // Optionally flash a message or perform other actions } // Dynamic event name with component property #[On('post-updated.{post.id}')] public function handlePostUpdated() { // Only triggered for events matching this post's ID } }; ?> <div> <p>Total Posts: {{ $postCount }}</p> <!-- Listen for child component events --> <livewire:post.create @post-created="$refresh" /> <!-- Dispatch events from Blade --> <button wire:click="$dispatch('refresh-stats')">Refresh</button> </div> ``` ## Nesting Components Components can be nested within each other with props, slots, and reactive data binding. ```php <?php // resources/views/components/⚡todo-list.blade.php use Livewire\Attributes\Computed; use Livewire\Component; new class extends Component { public string $newTodo = ''; #[Computed] public function todos() { return auth()->user()->todos; } public function addTodo() { auth()->user()->todos()->create(['content' => $this->pull('newTodo')]); unset($this->todos); } public function removeTodo(int $todoId) { auth()->user()->todos()->where('id', $todoId)->delete(); unset($this->todos); } }; ?> <div> <form wire:submit="addTodo"> <input type="text" wire:model="newTodo"> <button type="submit">Add</button> </form> @foreach ($this->todos as $todo) <!-- Pass props and key to child components --> <livewire:todo-item :todo="$todo" :wire:key="$todo->id" /> @endforeach </div> ``` ```php <?php // resources/views/components/⚡todo-item.blade.php use Livewire\Attributes\Reactive; use Livewire\Attributes\Modelable; use Livewire\Component; use App\Models\Todo; new class extends Component { #[Reactive] // Re-render when parent updates this prop public Todo $todo; public bool $editing = false; public string $editContent = ''; public function startEdit() { $this->editing = true; $this->editContent = $this->todo->content; } public function saveEdit() { $this->todo->update(['content' => $this->editContent]); $this->editing = false; } public function delete() { // Call parent's method directly // or dispatch event: $this->dispatch('remove-todo', todoId: $this->todo->id); } }; ?> <div> @if ($editing) <input type="text" wire:model="editContent" wire:keydown.enter="saveEdit"> <button wire:click="saveEdit">Save</button> @else <span>{{ $todo->content }}</span> <button wire:click="startEdit">Edit</button> <button wire:click="$parent.removeTodo({{ $todo->id }})">Remove</button> @endif </div> ``` ## Lifecycle Hooks Lifecycle hooks allow you to execute code at specific points during a component's lifecycle. ```php <?php // resources/views/components/⚡user-editor.blade.php use Livewire\Component; use App\Models\User; new class extends Component { public User $user; public string $name = ''; public string $email = ''; // Called once when component is first created public function mount(User $user) { $this->user = $user; $this->name = $user->name; $this->email = $user->email; } // Called at the beginning of every request (initial and subsequent) public function boot() { // Initialize services, set up listeners, etc. } // Called when component is re-hydrated (subsequent requests only) public function hydrate() { // Restore non-serializable state } // Called before any property is updated public function updating(string $property, mixed $value) { if ($property === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new \Exception('Invalid email format'); } } // Called after any property is updated public function updated(string $property, mixed $value) { if ($property === 'name') { $this->name = ucwords($this->name); } } // Called after a specific property is updated public function updatedEmail(string $value) { $this->email = strtolower($value); } // Called before render public function rendering($view, $data) { // Modify view or data before rendering } // Called after render public function rendered($view, $html) { // Process rendered HTML } // Called at the end of every request public function dehydrate() { // Clean up before serialization } // Handle exceptions public function exception(\Throwable $e, callable $stopPropagation) { if ($e instanceof ValidationException) { $stopPropagation(); $this->addError('form', $e->getMessage()); } } }; ``` ## Loading States Livewire automatically adds data-loading attributes to elements that trigger network requests. Use CSS or wire:loading for loading indicators. ```blade <?php // resources/views/components/⚡search.blade.php use Livewire\Component; new class extends Component { public string $query = ''; public array $results = []; public function search() { sleep(1); // Simulate slow search $this->results = Post::where('title', 'like', "%{$this->query}%")->get()->toArray(); } }; ?> <div> <form wire:submit="search"> <input type="text" wire:model="query"> <!-- Using wire:loading directive --> <button type="submit"> <span wire:loading.remove>Search</span> <span wire:loading>Searching...</span> </button> <!-- Target specific actions --> <span wire:loading wire:target="search">Loading results...</span> </form> <!-- Using data-loading with Tailwind (recommended) --> <button type="submit" class="data-loading:opacity-50 data-loading:cursor-wait"> Search </button> <!-- Hide content while loading --> <div class="not-data-loading:hidden"> <svg class="animate-spin">...</svg> </div> <!-- Style parent based on child loading state --> <div class="has-data-loading:bg-gray-100"> <button wire:click="search">Search</button> </div> <ul> @foreach ($results as $result) <li wire:key="{{ $result['id'] }}">{{ $result['title'] }}</li> @endforeach </ul> </div> ``` ## SPA Navigation with wire:navigate Add SPA-like navigation between pages without full page reloads using wire:navigate. ```blade <!-- Layout file: resources/views/layouts/app.blade.php --> <!DOCTYPE html> <html> <head> <title>{{ $title ?? config('app.name') }}</title> @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles </head> <body> <nav> <!-- Add wire:navigate for SPA-like navigation --> <a href="/" wire:navigate>Dashboard</a> <a href="/posts" wire:navigate>Posts</a> <a href="/users" wire:navigate>Users</a> <!-- Prefetch on hover for even faster navigation --> <a href="/settings" wire:navigate.hover>Settings</a> <!-- Style active links automatically with data-current --> <a href="/posts" wire:navigate class="data-current:font-bold data-current:text-blue-600"> Posts </a> </nav> <main> {{ $slot }} </main> <!-- Persist elements across page navigations --> @persist('audio-player') <audio id="player" src="{{ $audioSrc }}" controls></audio> @endpersist @livewireScripts </body> </html> ``` ```php // Redirect with navigation (no full page reload) public function save() { // ... save logic return $this->redirect('/posts', navigate: true); } ``` ## Real-time Validation Livewire validates properties automatically when using #[Validate] attributes with live or blur modifiers. ```php <?php // resources/views/components/⚡registration.blade.php use Livewire\Attributes\Validate; use Livewire\Component; use Illuminate\Validation\Rules\Password; new class extends Component { #[Validate('required|min:2|max:255')] public string $name = ''; #[Validate('required|email|unique:users,email')] public string $email = ''; #[Validate('required|min:8|confirmed')] public string $password = ''; public string $password_confirmation = ''; // Custom validation with rules() method for complex rules protected function rules() { return [ 'password' => ['required', Password::min(8)->mixedCase()->numbers()], ]; } // Custom messages protected function messages() { return [ 'email.unique' => 'This email is already registered.', 'password.min' => 'Password must be at least 8 characters.', ]; } public function register() { $validated = $this->validate(); User::create([ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => bcrypt($validated['password']), ]); return redirect('/dashboard'); } }; ?> <form wire:submit="register"> <div> <label>Name</label> <input type="text" wire:model.blur="name"> @error('name') <span class="text-red-500">{{ $message }}</span> @enderror </div> <div> <label>Email</label> <input type="email" wire:model.blur="email"> @error('email') <span class="text-red-500">{{ $message }}</span> @enderror </div> <div> <label>Password</label> <input type="password" wire:model.blur="password"> @error('password') <span class="text-red-500">{{ $message }}</span> @enderror </div> <div> <label>Confirm Password</label> <input type="password" wire:model.blur="password_confirmation"> </div> <button type="submit">Register</button> </form> ``` ## Alpine.js Integration Livewire bundles Alpine.js and exposes the $wire object for client-side interactivity. ```blade <?php // resources/views/components/⚡counter.blade.php use Livewire\Component; new class extends Component { public int $count = 0; public function increment() { $this->count++; } public function getDoubleCount() { return $this->count * 2; } }; ?> <div x-data="{ localCount: 0 }"> <!-- Access Livewire properties from Alpine --> <p>Server count: <span x-text="$wire.count"></span></p> <p>Local count: <span x-text="localCount"></span></p> <!-- Call Livewire methods from Alpine --> <button x-on:click="$wire.increment()">Increment (Server)</button> <button x-on:click="localCount++">Increment (Local)</button> <!-- Set Livewire properties from Alpine --> <button x-on:click="$wire.count = 0">Reset Server Count</button> <button x-on:click="$wire.set('count', 10)">Set to 10</button> <!-- Await return values from Livewire methods --> <button x-on:click="alert(await $wire.getDoubleCount())"> Show Double </button> <!-- Character counter example --> <input type="text" wire:model="title"> <span x-text="$wire.title.length + ' characters'"></span> <!-- Listen for Livewire events in Alpine --> <div x-on:post-created.window="alert('Post created!')"></div> <!-- Dispatch events to Livewire from Alpine --> <button x-on:click="$dispatch('refresh-posts')">Refresh</button> </div> ``` ## Testing Components Livewire provides a fluent testing API for component testing. ```php <?php // tests/Feature/CreatePostTest.php namespace Tests\Feature; use App\Livewire\CreatePost; use App\Models\Post; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Livewire\Livewire; use Tests\TestCase; class CreatePostTest extends TestCase { use RefreshDatabase; public function test_can_create_post() { $user = User::factory()->create(); Livewire::actingAs($user) ->test(CreatePost::class) ->set('title', 'My New Post') ->set('content', 'This is the post content.') ->call('save') ->assertHasNoErrors() ->assertRedirect('/posts'); $this->assertDatabaseHas('posts', [ 'title' => 'My New Post', 'user_id' => $user->id, ]); } public function test_title_is_required() { Livewire::test(CreatePost::class) ->set('content', 'Some content') ->call('save') ->assertHasErrors(['title' => 'required']); } public function test_dispatches_event_on_save() { $user = User::factory()->create(); Livewire::actingAs($user) ->test(CreatePost::class) ->set('title', 'Test Post') ->set('content', 'Test content') ->call('save') ->assertDispatched('post-created'); } public function test_can_listen_for_events() { Livewire::test(Dashboard::class) ->assertSee('Posts: 0') ->dispatch('post-created') ->assertSee('Posts: 1'); } public function test_renders_correctly() { $post = Post::factory()->create(['title' => 'Hello World']); Livewire::test(ShowPost::class, ['post' => $post]) ->assertSee('Hello World') ->assertSet('post.id', $post->id); } } ``` ## Summary Livewire is designed for Laravel developers who want to build modern, reactive web applications without leaving PHP. The primary use cases include building interactive forms with real-time validation, creating dynamic dashboards and data tables, implementing search and filtering interfaces, building multi-step wizards, and adding AJAX functionality to existing Laravel applications. Livewire excels when you need reactivity but want to leverage your existing Laravel and Blade knowledge rather than learning a JavaScript framework. Integration patterns typically involve creating components for specific UI sections, using computed properties for database queries, implementing form objects for complex form logic, and utilizing events for cross-component communication. For SPA-like experiences, wire:navigate provides seamless page transitions without full reloads. The framework integrates naturally with Laravel's authentication, authorization, validation, and Eloquent ORM, making it an ideal choice for teams already invested in the Laravel ecosystem who want to add modern interactivity to their applications.