skills/laravel-livewire/SKILL.md
Full-stack Laravel framework for building dynamic, reactive interfaces using PHP without writing JavaScript. Use when creating or modifying Livewire components, implementing data binding with wire:model, working with lifecycle hooks, building forms with validation, handling events and parent-child communication, implementing file uploads/pagination/lazy loading, writing tests, or optimizing performance. Supports Laravel Livewire v4+ development. Keywords: Livewire, wire:model, wire:click, livewire component, Alpine.js integration, wire:submit, real-time validation, computed properties, Laravel Livewire.
npx skillsauth add 1naichii/ai-code-tools laravel-livewireInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build dynamic, reactive Laravel interfaces using only PHP. Livewire v4+ handles server-side rendering with automatic client-side updates via hydration/dehydration—no JavaScript required.
composer require livewire/livewire
php artisan livewire:layout # Creates resources/views/layouts/app.blade.php
# Single-file component (default)
php artisan make:livewire CreatePost
# Page component
php artisan make:livewire pages::post.create
# Multi-file component
php artisan make:livewire CreatePost --mfc
# Class-based component (traditional)
php artisan make:livewire CreatePost --class
<?php
use Livewire\Component;
new class extends Component {
public string $title = '';
public string $content = '';
public function save()
{
$this->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
Post::create($this->only(['title', 'content']));
return $this->redirect('/posts');
}
};
?>
<form wire:submit="save">
<input type="text" wire:model="title">
@error('title') <span class="error">{{ $message }}</span> @enderror
<textarea wire:model="content"></textarea>
@error('content') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Save</button>
</form>
Single-file components (resources/views/components/post/⚡create.blade.php):
Multi-file components (resources/views/components/post/⚡create/):
Class-based components (app/Livewire/CreatePost.php):
// Public properties - accessible in template as $property
public $title = '';
// Protected properties - accessible as $this->property, not sent to client
protected $apiKey = 'secret';
// Typed properties
public string $email = '';
public int $count = 0;
public ?Post $post; // Auto-locks ID
// Reset properties
$this->reset('title', 'content');
$value = $this->pull('title'); // Get and reset
| Hook | When It Runs |
|------|--------------|
| mount() | First load only - receive props/route params |
| boot() | Every request (initial + subsequent) |
| hydrate() | Beginning of subsequent requests |
| dehydrate() | End of every request |
| updating($prop) | Before property update |
| updated($prop) | After property update |
| rendering() | Before render() |
| rendered() | After render() |
| exception($e) | When exception thrown |
public function mount(Post $post)
{
$this->post = $post;
$this->title = $post->title;
}
public function updatedTitle($value)
{
$this->title = strtolower($value);
}
Memoized derived values—accessed via $this->property.
use Livewire\Attributes\Computed;
#[Computed]
public function posts()
{
return Post::all(); // Runs once per request
}
#[Computed(persist: true, seconds: 3600)]
public function cachedData()
{
return ExpensiveModel::all();
}
Usage in blade: @foreach ($this->posts as $post)
| Modifier | Behavior |
|----------|----------|
| (default) | Updates only on action submit |
| .live | Updates as user types (150ms debounce) |
| .blur | Updates when user clicks away |
| .change | Updates immediately on selection |
| .debounce.500ms | Custom debounce duration |
| .number | Cast to int on server |
| .boolean | Cast to bool on server |
<input type="text" wire:model="title">
<input type="email" wire:model.live="email"> <!-- Live validation -->
<input type="text" wire:model.blur="title"> <!-- On blur -->
<input type="text" wire:model.live.debounce.500ms="search">
Use wire:key when one select depends on another.
<select wire:model.live="selectedState">
@foreach(State::all() as $state)
<option value="{{ $state->id }}">{{ $state->label }}</option>
@endforeach
</select>
<select wire:model.live="selectedCity" wire:key="{{ $selectedState }}">
@foreach(City::whereStateId($selectedState)->get() as $city)
<option value="{{ $city->id }}">{{ $city->label }}</option>
@endforeach
</select>
<button wire:click="save">Save</button>
<input wire:keydown.enter="search">
<form wire:submit="submitForm">
<button wire:click="delete({{ $post->id }})">Delete</button>
<!-- Key modifiers -->
<input wire:keydown.enter="search">
<input wire:keydown.shift.enter="...">
<!-- Event modifiers -->
<button wire:click.prevent="save">
<button wire:click.stop="...">
<button wire:click.window="...">
<button wire:click.once="...">
<button wire:click.debounce.250ms="...">
From PHP:
$this->dispatch('post-created', postId: $post->id);
$this->dispatch('post-created')->to(Dashboard::class); // Direct to component
From Blade (client-side):
<button wire:click="$dispatch('post-created', { id: {{ $post->id }} })">
Listening in PHP:
use Livewire\Attributes\On;
#[On('post-created')]
public function handlePostCreated($postId)
{
// Handle event
}
Listening in Blade:
<livewire:post-list @post-created="$refresh" />
<!-- Passing props -->
<livewire:todo-item :$post />
<!-- Reactive props (child updates when parent changes) -->
<?php
use Livewire\Attributes\Reactive;
#[Reactive]
public $todos;
?>
<!-- Direct parent access -->
<button wire:click="$parent.remove({{ $id }})">Remove</button>
use Livewire\Attributes\Validate;
new class extends Component {
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|email', message: 'Please enter a valid email')]
public $email = '';
public function save()
{
$this->validate(); // Runs all rules
Post::create($this->only(['title', 'email']));
}
};
<input type="text" wire:model.live="title">
@error('title') <span class="error">{{ $message }}</span> @enderror
public function updated($property)
{
$this->validateOnly($property);
}
Extract form logic into reusable classes:
php artisan livewire:form PostForm
// app/Livewire/Forms/PostForm.php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
public function store()
{
$this->validate();
Post::create($this->only(['title', 'content']));
}
}
<input type="text" wire:model="form.title">
@error('form.title') <span class="error">{{ $message }}</span> @enderror
<button wire:click="save">
Save
<span wire:loading>Saving...</span>
</button>
<!-- Target specific action -->
<div wire:loading wire:target="removePhoto">Removing...</div>
<!-- CSS attribute -->
<button class="data-loading:opacity-50">Save</button>
<livewire:revenue-chart lazy />
use Livewire\Attributes\Lazy;
#[Lazy]
class RevenueChart extends Component
{
public function placeholder()
{
return view('livewire.placeholders.skeleton');
}
}
<div wire:poll>{{ $count }}</div> <!-- Every 2.5s -->
<div wire:poll.15s>{{ $count }}</div> <!-- Custom interval -->
<div wire:poll.visible>{{ $count }}</div> <!-- Only when visible -->
<div wire:poll.keep-alive>{{ $count }}</div> <!-- Keep in background -->
use Livewire\WithFileUploads;
class UploadPhoto extends Component
{
use WithFileUploads;
#[Validate('image|max:1024')] // 1MB max
public $photo;
public function save()
{
$this->photo->store(path: 'photos');
}
}
<form wire:submit="save">
@if ($photo)
<img src="{{ $photo->temporaryUrl() }}">
@endif
<input type="file" wire:model="photo">
</form>
use Livewire\WithPagination;
class ShowPosts extends Component
{
use WithPagination;
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::paginate(10),
]);
}
}
{{ $posts->links() }}
<div x-data="{ expanded: false }">
<button @click="expanded = !expanded">Toggle</button>
<div x-show="expanded">
{{ $content }}
</div>
</div>
Access Livewire from Alpine:
<input x-on:blur="$wire.save()">
<span x-text="$wire.title.length"></span>
use Livewire\Livewire;
test('component renders', function () {
Livewire::test(CreatePost::class)
->assertStatus(200);
});
test('can create post', function () {
Livewire::test(CreatePost::class)
->set('title', 'Test Post')
->call('save')
->assertRedirect('/posts');
});
test('validation works', function () {
Livewire::test(CreatePost::class)
->set('title', '')
->call('save')
->assertHasErrors('title');
});
// routes/web.php
Route::livewire('/posts/create', 'pages::post.create');
Route::livewire('/posts/{id}', 'pages::post.show');
Route::livewire('/posts/{post}', 'pages::post.edit'); // Model binding
| Attribute | Purpose |
|-----------|---------|
| #[Validate('rule')] | Add validation rules to properties |
| #[Computed] | Create memoized derived properties |
| #[Computed(persist: true)] | Cache computed across requests |
| #[Locked] | Prevent client-side modification |
| #[Reactive] | Props update when parent changes |
| #[On('event')] | Listen for dispatched events |
| #[Lazy] | Defer component loading |
| #[Session] | Persist properties in session |
| #[Url] | Sync with query string |
| #[Renderless] | Skip re-render after action |
| #[Async] | Execute action in parallel |
| #[Layout('name')] | Specify custom layout |
| #[Title('Title')] | Set page title |
| #[Js] | Return JSON for JavaScript consumption |
$this — use $this->posts, not $posts.live modifier#[Reactive] attributeonly() or except() to limit data sent to client#[Locked] for sensitive IDs to prevent manipulation#[Validate] or rules() method.blur instead of .live when real-time isn't neededwire:key for list items to prevent DOM thrashing#[Computed(persist: true)]See references/core.md — components, properties, lifecycle, actions
See references/forms.md — form handling, validation, file uploads
See references/advanced.md — nesting, events, computed properties, pagination
See references/directives.md — all wire:* directives
See references/attributes.md — all PHP attributes
See references/integration.md — Alpine.js, JavaScript, security
See references/testing.md — Pest/PHPUnit patterns
development
Lucide icon library integration for Laravel Blade templates. Use when working with Lucide icons in Laravel applications, Blade components with the x-lucide- prefix, icon styling with Tailwind CSS, dynamic icon rendering in Blade, or any Laravel view work requiring SVG icons. Keywords include lucide icons, blade icons, x-lucide, SVG icons Laravel, blade-lucide-icons, mallardduck/blade-lucide-icons.
tools
Build modern monolith applications with Inertia.js - combining server-side frameworks (Laravel, Rails, etc.) with React/Vue/Svelte frontends without building APIs. Use when creating Inertia pages and layouts, working with Link component for navigation, building forms with Form component or useForm hook, handling validation and errors, managing shared data and props, implementing authentication and authorization, using manual visits with router, working with partial reloads, setting up persistent layouts, or configuring client-side setup.
development
Fetch web content with automatic markdown version detection using curl. Use when Claude needs to retrieve documentation from websites that offer both HTML and markdown formats. First checks if a .md version exists (more efficient and cleaner), then falls back to HTML if unavailable. Ideal for fetching documentation from sites like ui.shadcn.com, GitHub wikis, or any documentation site that mirrors content in markdown format.
development
CSS component library for Tailwind CSS 4 providing pre-built UI components with semantic class names. Use when building web interfaces with HTML/Tailwind that need: (1) Rapid UI development with consistent styling, (2) Accessible component patterns (buttons, forms, modals, etc.), (3) Theme-aware color systems that work across light/dark modes, (4) Responsive layouts with Tailwind utilities. Works with daisyUI v5+ which requires Tailwind CSS v4+.