laravel-livewire
Laravel Livewire v4以降で、PHPのみを使いJavaScriptなしで動的なWebインターフェースを開発するSkill。
📜 元の英語説明(参考)
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.
🇯🇵 日本人クリエイター向け解説
Laravel Livewire v4以降で、PHPのみを使いJavaScriptなしで動的なWebインターフェースを開発するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o laravel-livewire.zip https://jpskill.com/download/6157.zip && unzip -o laravel-livewire.zip && rm laravel-livewire.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/6157.zip -OutFile "$d\laravel-livewire.zip"; Expand-Archive "$d\laravel-livewire.zip" -DestinationPath $d -Force; ri "$d\laravel-livewire.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
laravel-livewire.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
laravel-livewireフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-17
- 取得日時
- 2026-05-17
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Laravel Livewire
概要
PHPのみを使用して、動的でリアクティブなLaravelインターフェースを構築します。Livewire v4+は、ハイドレーション/デハイドレーションによる自動クライアントサイド更新でサーバーサイドレンダリングを処理します。JavaScriptは不要です。
クイックスタート
インストール
composer require livewire/livewire
php artisan livewire:layout # resources/views/layouts/app.blade.php を作成します
コンポーネントの作成
# シングルファイルコンポーネント (デフォルト)
php artisan make:livewire CreatePost
# ページコンポーネント
php artisan make:livewire pages::post.create
# マルチファイルコンポーネント
php artisan make:livewire CreatePost --mfc
# クラスベースコンポーネント (従来型)
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>
コアコンセプト
コンポーネントの構造
シングルファイルコンポーネント (resources/views/components/post/⚡create.blade.php):
- PHPクラスとBladeテンプレートが1つのファイルに
- 稲妻マーク (⚡) はオプションで、設定で無効にできます
マルチファイルコンポーネント (resources/views/components/post/⚡create/):
- PHP、Blade、JS、CSSが個別のファイルに
- 大規模なJavaScriptを伴う大きなコンポーネントに適しています
クラスベースコンポーネント (app/Livewire/CreatePost.php):
- 従来のLaravel構造
- Livewire v2/v3開発者には馴染み深い
プロパティ管理
// Public properties - テンプレートで $property としてアクセス可能
public $title = '';
// Protected properties - $this->property としてアクセス可能、クライアントには送信されない
protected $apiKey = 'secret';
// 型付きプロパティ
public string $email = '';
public int $count = 0;
public ?Post $post; // IDを自動ロック
// プロパティのリセット
$this->reset('title', 'content');
$value = $this->pull('title'); // 取得してリセット
ライフサイクルフック
| フック | 実行タイミング |
|---|---|
mount() |
初回ロード時のみ - props/ルートパラメータを受け取る |
boot() |
すべてのリクエスト (初回 + 以降) |
hydrate() |
以降のリクエストの開始時 |
dehydrate() |
すべてのリクエストの終了時 |
updating($prop) |
プロパティ更新前 |
updated($prop) |
プロパティ更新後 |
rendering() |
render() の前 |
rendered() |
render() の後 |
exception($e) |
例外がスローされた時 |
public function mount(Post $post)
{
$this->post = $post;
$this->title = $post->title;
}
public function updatedTitle($value)
{
$this->title = strtolower($value);
}
算出プロパティ
メモ化された派生値 — $this->property を介してアクセスします。
use Livewire\Attributes\Computed;
#[Computed]
public function posts()
{
return Post::all(); // リクエストごとに1回実行
}
#[Computed(persist: true, seconds: 3600)]
public function cachedData()
{
return ExpensiveModel::all();
}
Bladeでの使用法: @foreach ($this->posts as $post)
データバインディング
wire:model修飾子
| 修飾子 | 動作 |
|---|---|
| (デフォルト) | アクション送信時のみ更新 |
.live |
ユーザーが入力するたびに更新 (150msのデバウンス) |
.blur |
ユーザーがクリックして離れたときに更新 |
.change |
選択時に即座に更新 |
.debounce.500ms |
カスタムデバウンス期間 |
.number |
サーバーでintにキャスト |
.boolean |
サーバーでboolにキャスト |
<input type="text" wire:model="title">
<input type="email" wire:model.live="email"> <!-- ライブバリデーション -->
<input type="text" wire:model.blur="title"> <!-- フォーカスが外れたとき -->
<input type="text" wire:model.live.debounce.500ms="search">
依存するセレクト (重要!)
あるセレクトが別のセレクトに依存する場合、wire:key を使用します。
<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>
イベント修飾子
<!-- キー修飾子 -->
<input wire:keydown.enter="search">
<input wire:keydown.shift.enter="...">
<!-- イベント修飾子 -->
<button wire:click.prevent="save">
<button wire:click.stop="...">
<button wire:click.window="...">
<button wire:click.once="...">
<button wire:click.debounce.250ms="...">
イベントのディスパッチ
PHPから:
$this->dispatch('post-created', postId: $post->id);
$this->dispatch('post-created')->to(Dashboard::class); // コンポーネントに直接
Bladeから (クライアントサイド):
<button wire:click="$dispatch('post-created', { id: {{ $post->id }} })">
PHPでのリスニング:
use Livewire\Attributes\On;
#[On('post-created')]
public function handlePostCreated($postId)
{
// イベントを処理
}
Bladeでのリスニング:
<livewire:post-list @post-created="$refresh" />
親子コンポーネント間の通信
<!-- propsの受け渡し -->
<livewire:todo-item :$post />
<!-- リアクティブなprops (親が変更されると子が更新される) -->
<?php
use Livewire\Attributes\Reactive;
#[Reactive]
public $todos;
?>
<!-- 親への直接アクセス -->
<butto 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Laravel Livewire
Overview
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.
Quick Start
Installation
composer require livewire/livewire
php artisan livewire:layout # Creates resources/views/layouts/app.blade.php
Create a Component
# 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
Basic Component Pattern
<?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>
Core Concepts
Component Structure
Single-file components (resources/views/components/post/⚡create.blade.php):
- PHP class and Blade template in one file
- Lightning bolt (⚡) is optional and can be disabled in config
Multi-file components (resources/views/components/post/⚡create/):
- Separate files for PHP, Blade, JS, CSS
- Better for large components with significant JavaScript
Class-based components (app/Livewire/CreatePost.php):
- Traditional Laravel structure
- Familiar for Livewire v2/v3 developers
Property Management
// 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
Lifecycle Hooks
| 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);
}
Computed Properties
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)
Data Binding
wire:model Modifiers
| 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">
Dependent Selects (Important!)
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>
Actions & Events
Event Listeners
<button wire:click="save">Save</button>
<input wire:keydown.enter="search">
<form wire:submit="submitForm">
<button wire:click="delete({{ $post->id }})">Delete</button>
Event Modifiers
<!-- 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="...">
Dispatching Events
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" />
Parent-Child Communication
<!-- 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>
Forms & Validation
Validation with Attributes
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']));
}
};
Real-Time Validation
<input type="text" wire:model.live="title">
@error('title') <span class="error">{{ $message }}</span> @enderror
public function updated($property)
{
$this->validateOnly($property);
}
Form Objects
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
Loading States
<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>
Advanced Features
Lazy Loading
<livewire:revenue-chart lazy />
use Livewire\Attributes\Lazy;
#[Lazy]
class RevenueChart extends Component
{
public function placeholder()
{
return view('livewire.placeholders.skeleton');
}
}
Polling
<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 -->
File Uploads
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>
Pagination
use Livewire\WithPagination;
class ShowPosts extends Component
{
use WithPagination;
public function render()
{
return view('livewire.show-posts', [
'posts' => Post::paginate(10),
]);
}
}
{{ $posts->links() }}
Alpine.js Integration
<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>
Testing
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');
});
Routing
// 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
PHP Attributes Reference
| 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 |
Common Gotchas
- Computed properties require
$this— use$this->posts, not$posts - Default wire:model doesn't update as you type — use
.livemodifier - Dependent selects need wire:key — prevents stale options
- Props aren't reactive by default — use
#[Reactive]attribute - Always validate/authorize properties — treat as user input
- Use
only()orexcept()to limit data sent to client
Security Best Practices
- Always authorize action parameters — users can call any public method
- Use
#[Locked]for sensitive IDs to prevent manipulation - Mark dangerous methods as protected/private — prevents client access
- Validate all input — use
#[Validate]orrules()method - Never trust client-side data — properties are user input
Performance Tips
- Use computed properties for expensive queries
- Lazy load components below the fold
- Use
.blurinstead of.livewhen real-time isn't needed - Avoid storing large Eloquent collections as properties
- Use
wire:keyfor list items to prevent DOM thrashing - Debounce live updates for better performance
- Cache expensive operations with
#[Computed(persist: true)]
Resources
For Component Architecture
See references/core.md — components, properties, lifecycle, actions
For Forms & Validation
See references/forms.md — form handling, validation, file uploads
For Advanced Features
See references/advanced.md — nesting, events, computed properties, pagination
For Directives
See references/directives.md — all wire:* directives
For Attributes
See references/attributes.md — all PHP attributes
For Integration
See references/integration.md — Alpine.js, JavaScript, security
For Testing
See references/testing.md — Pest/PHPUnit patterns