Blade Livewire
Blade templating engine and Livewire for building dynamic server-rendered UI in Laravel applications
You are an expert in Blade and Livewire for building Laravel applications. You build server-rendered UIs that are composable, secure by default against XSS, and interactive without the complexity overhead of a full JavaScript framework.
## Key Points
- Use Blade components (`<x-component>`) over `@include` for reusable UI with typed props.
- Escape output with `{{ }}` by default; use `{!! !!}` only for trusted, already-sanitized HTML.
- Use `@push` / `@stack` for page-specific CSS and JS rather than loading everything globally.
- In Livewire, use `wire:key` on every item in loops so the diffing engine tracks DOM elements correctly.
- Use Livewire Form Objects to encapsulate validation and persistence logic outside the component.
- Prefer `wire:model.live.debounce` over `wire:model.live` for text inputs to reduce server round-trips.
- Use `#[Computed]` for derived data so it is cached within a single request lifecycle.
- **XSS via `{!! !!}`**: Never render unsanitized user input with raw output.
- **Missing `wire:key`**: Omitting keys in `@foreach` loops causes Livewire to misidentify DOM nodes, leading to visual glitches.
- **Overloading Livewire components**: A single component managing dozens of properties becomes hard to maintain; split into child components.
- **N+1 queries in Blade loops**: Always eager-load relationships in the controller or Livewire component, not inside the template.
- **Layout inheritance confusion**: Do not mix `@extends` and component layouts in the same view hierarchy.skilldb get php-laravel-skills/Blade LivewireFull skill: 343 linesBlade Templates & Livewire — PHP/Laravel
You are an expert in Blade and Livewire for building Laravel applications. You build server-rendered UIs that are composable, secure by default against XSS, and interactive without the complexity overhead of a full JavaScript framework.
Core Philosophy
Blade and Livewire represent a deliberate architectural choice: keep the rendering logic on the server, close to the data and the business rules, and send HTML to the browser. This is not a compromise or a limitation -- it is a productivity strategy. When the view layer lives in the same process as the application logic, there is no API to design, no serialization boundary to manage, no client-side state to synchronize. Blade components compose just like any other software: small, focused, reusable pieces that accept typed props and render predictable output. The result is a UI architecture that a single developer or small team can maintain without a separate frontend build pipeline.
Security in the view layer is about defaults, not discipline. Blade's {{ }} syntax escapes output by default, which means XSS protection is automatic for every interpolation. The {!! !!} raw output syntax exists for trusted content, but its use should be rare and reviewed carefully. Livewire's property binding sends data to the server for processing, so the server always has the final say on validation and authorization. This server-authority model means that client-side tampering cannot bypass business rules -- a significant security advantage over architectures where the client manages its own state and the server trusts what it receives.
Livewire's reactivity model is powerful but has a cost: every interaction triggers an HTTP round-trip to the server. This is fast on modern connections and for most use cases, but it requires awareness. Debouncing text inputs, using wire:model.blur instead of wire:model.live where instant feedback is not needed, and keeping component state small all reduce unnecessary server round-trips. The team should understand that Livewire trades client-side complexity for server load, and design components accordingly -- keeping each component focused on a single interactive concern rather than building monolithic page-level components.
Overview
Blade is Laravel's templating engine that compiles to plain PHP for zero overhead. Livewire is a full-stack framework that lets you build reactive, dynamic interfaces using PHP classes and Blade templates without writing custom JavaScript.
Core Concepts
Blade Basics
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<title>@yield('title', config('app.name'))</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('styles')
</head>
<body>
@include('partials.navigation')
<main class="container mx-auto py-8">
@yield('content')
</main>
@stack('scripts')
</body>
</html>
{{-- resources/views/posts/show.blade.php --}}
@extends('layouts.app')
@section('title', $post->title)
@section('content')
<article>
<h1>{{ $post->title }}</h1>
<p class="text-gray-600">By {{ $post->author->name }} on {{ $post->created_at->format('M d, Y') }}</p>
<div>{!! $post->rendered_body !!}</div>
</article>
@endsection
Blade Components
{{-- resources/views/components/alert.blade.php --}}
@props([
'type' => 'info',
'dismissible' => false,
])
@php
$classes = match ($type) {
'success' => 'bg-green-100 text-green-800 border-green-300',
'error' => 'bg-red-100 text-red-800 border-red-300',
'warning' => 'bg-yellow-100 text-yellow-800 border-yellow-300',
default => 'bg-blue-100 text-blue-800 border-blue-300',
};
@endphp
<div {{ $attributes->merge(['class' => "rounded border p-4 {$classes}"]) }} role="alert">
{{ $slot }}
@if ($dismissible)
<button type="button" onclick="this.parentElement.remove()">×</button>
@endif
</div>
{{-- Usage --}}
<x-alert type="error" class="mt-4">
Something went wrong. Please try again.
</x-alert>
Class-Based Components
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class PostCard extends Component
{
public function __construct(
public Post $post,
public bool $showExcerpt = true,
) {}
public function isNew(): bool
{
return $this->post->created_at->gt(now()->subDays(7));
}
public function render(): View
{
return view('components.post-card');
}
}
{{-- Usage --}}
<x-post-card :post="$post" :show-excerpt="false" />
Blade Directives and Conditionals
@auth
<p>Welcome, {{ auth()->user()->name }}</p>
@endauth
@guest
<a href="{{ route('login') }}">Sign In</a>
@endguest
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan
@forelse ($posts as $post)
<x-post-card :post="$post" />
@empty
<p>No posts found.</p>
@endforelse
{{ $posts->links() }} {{-- Pagination --}}
Implementation Patterns
Livewire Component
namespace App\Livewire;
use App\Models\Post;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class PostList extends Component
{
use WithPagination;
#[Url]
public string $search = '';
#[Url]
public string $sortBy = 'latest';
public function updatedSearch(): void
{
$this->resetPage();
}
#[Computed]
public function posts()
{
return Post::published()
->with('author', 'tags')
->when($this->search, fn ($q, $search) =>
$q->where('title', 'like', "%{$search}%")
)
->when($this->sortBy === 'popular', fn ($q) =>
$q->orderByDesc('views')
, fn ($q) =>
$q->latest()
)
->paginate(12);
}
public function render()
{
return view('livewire.post-list');
}
}
{{-- resources/views/livewire/post-list.blade.php --}}
<div>
<div class="flex gap-4 mb-6">
<input
type="text"
wire:model.live.debounce.300ms="search"
placeholder="Search posts..."
class="border rounded px-4 py-2"
/>
<select wire:model.live="sortBy" class="border rounded px-4 py-2">
<option value="latest">Latest</option>
<option value="popular">Most Popular</option>
</select>
</div>
<div class="grid grid-cols-3 gap-6">
@foreach ($this->posts as $post)
<x-post-card :post="$post" wire:key="post-{{ $post->id }}" />
@endforeach
</div>
{{ $this->posts->links() }}
</div>
Livewire Form Object
namespace App\Livewire\Forms;
use App\Models\Post;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PostForm extends Form
{
public ?Post $post = null;
#[Validate('required|string|max:255')]
public string $title = '';
#[Validate('required|string|min:10')]
public string $body = '';
#[Validate('array')]
public array $tags = [];
public function setPost(Post $post): void
{
$this->post = $post;
$this->title = $post->title;
$this->body = $post->body;
$this->tags = $post->tags->pluck('id')->toArray();
}
public function store(): Post
{
$this->validate();
$post = auth()->user()->posts()->create($this->only(['title', 'body']));
$post->tags()->sync($this->tags);
return $post;
}
public function update(): Post
{
$this->validate();
$this->post->update($this->only(['title', 'body']));
$this->post->tags()->sync($this->tags);
return $this->post;
}
}
Livewire Actions and Events
class CommentSection extends Component
{
public Post $post;
public string $newComment = '';
public function addComment(): void
{
$this->validate(['newComment' => 'required|string|max:1000']);
$this->post->comments()->create([
'user_id' => auth()->id(),
'body' => $this->newComment,
]);
$this->reset('newComment');
$this->dispatch('comment-added');
}
public function deleteComment(Comment $comment): void
{
$this->authorize('delete', $comment);
$comment->delete();
}
public function render()
{
return view('livewire.comment-section', [
'comments' => $this->post->comments()->with('user')->latest()->get(),
]);
}
}
Best Practices
- Use Blade components (
<x-component>) over@includefor reusable UI with typed props. - Escape output with
{{ }}by default; use{!! !!}only for trusted, already-sanitized HTML. - Use
@push/@stackfor page-specific CSS and JS rather than loading everything globally. - In Livewire, use
wire:keyon every item in loops so the diffing engine tracks DOM elements correctly. - Use Livewire Form Objects to encapsulate validation and persistence logic outside the component.
- Prefer
wire:model.live.debounceoverwire:model.livefor text inputs to reduce server round-trips. - Use
#[Computed]for derived data so it is cached within a single request lifecycle.
Common Pitfalls
- XSS via
{!! !!}: Never render unsanitized user input with raw output. - Missing
wire:key: Omitting keys in@foreachloops causes Livewire to misidentify DOM nodes, leading to visual glitches. - Overloading Livewire components: A single component managing dozens of properties becomes hard to maintain; split into child components.
- N+1 queries in Blade loops: Always eager-load relationships in the controller or Livewire component, not inside the template.
- Layout inheritance confusion: Do not mix
@extendsand component layouts in the same view hierarchy. - Large payloads in Livewire: Binding very large arrays or deeply nested objects to public properties inflates the wire payload. Use computed properties or pagination.
Anti-Patterns
-
Logic in templates — embedding complex conditionals, loops with business calculations, or service calls directly in Blade templates. Templates should receive prepared data and render it. Move computation to the controller, Livewire component, or a View Composer so the template remains a thin presentation layer.
-
The mega Livewire component — a single component with 20+ public properties managing a multi-step form, a data table, a modal, and a notification system. This produces bloated wire payloads, hard-to-trace reactivity bugs, and unmaintainable code. Split into focused child components that communicate via events.
-
Raw output for user content — using
{!! $userInput !!}to render user-submitted HTML without sanitization. This is a direct XSS vulnerability. If rich text is required, sanitize with a library like HTMLPurifier before storing, and only then render with raw output. -
Missing wire:key in loops — rendering
@foreachloops in Livewire withoutwire:keyon each item. Without keys, Livewire cannot correctly diff the DOM when items are added, removed, or reordered, leading to visual glitches, duplicated elements, and lost input state. -
Mixing layout strategies — using
@extendsinheritance in some views, component-based layouts in others, and Livewire full-page components with yet another layout approach. Pick one layout strategy for the application and use it consistently. Mixed strategies cause confusion about which layout is active and make global changes difficult.
Install this skill directly: skilldb add php-laravel-skills
Related Skills
API Resources
Laravel API resources and transformers for building consistent, well-structured JSON API responses
Authentication
Laravel authentication using Sanctum for API tokens and SPAs, and Fortify for web-based auth flows
Deployment
Deploying Laravel applications with Forge, Vapor, and general server deployment strategies
Eloquent ORM
Eloquent ORM patterns for models, relationships, query scoping, and database interactions in Laravel
Queues Jobs
Laravel queues, jobs, and background task processing with Redis, SQS, and Horizon
Routing Middleware
Laravel routing, route groups, resource controllers, and middleware for request filtering and authentication