Livewire 4 : le guide complet des nouveautés qui révolutionnent vos composants Laravel
Livewire 4 marque un tournant majeur pour le développement d’interfaces réactives en Laravel. Cette version, publiée le 14 janvier 2026 par Caleb Porzio, repense entièrement la façon d’écrire des composants : tout peut désormais tenir dans un seul fichier, avec du PHP, du Blade, du CSS scopé et du JavaScript contextuel.
Qu’est-ce que Livewire et pourquoi l’utiliser ?
Livewire permet de créer des interfaces dynamiques en PHP pur, sans écrire une ligne de JavaScript. Chaque composant Livewire communique avec le serveur via AJAX de manière transparente : vous modifiez une propriété PHP, et le DOM se met à jour automatiquement.
Les cas d’usage typiques :
- Formulaires avec validation temps réel
- Tableaux avec tri, filtres et pagination
- Modales interactives
- Dashboards avec données live
Avant Livewire 4, il fallait jongler entre une classe PHP et un fichier Blade séparé. Cette époque est révolue.
Installation de Livewire 4
Mettez à jour votre projet Laravel 12+ vers Livewire 4 :
# Installation ou mise à jour vers Livewire 4
composer require livewire/livewire:^4.0
# Vider le cache après mise à jour
php artisan optimize:clear
Pour un nouveau projet Laravel, le starter kit Livewire intègre directement la v4 :
# Créer un projet avec le starter kit Livewire
laravel new mon-projet --livewire
Single-File Components : tout dans un seul fichier
La révolution de Livewire 4, c’est le format single-file. Votre composant entier tient dans un fichier .blade.php préfixé d’un emoji ⚡ pour le distinguer des composants Blade classiques.
<?php
// resources/views/components/⚡compteur.blade.php
use Livewire\Component;
new class extends Component {
// Propriété réactive accessible dans le template
public int $compteur = 0;
// Action déclenchée par wire:click
public function incrementer(): void
{
$this->compteur++;
}
};
?>
<div>
<h2 class="titre">{{ $compteur }}</h2>
<button wire:click="incrementer">+1</button>
</div>
<style>
/* CSS scopé automatiquement au composant */
.titre {
font-size: 3rem;
color: #3b82f6;
}
</style>
<script>
// JavaScript avec accès direct à $wire via 'this'
this.$watch('compteur', (value) => {
if (value >= 10) {
console.log('Objectif atteint !')
}
})
</script>
Ce composant combine PHP, HTML, CSS scopé et JavaScript contextuel. Le CSS .titre ne fuite pas vers les autres composants. Le JavaScript accède au composant via this (alias de $wire).
Pour créer un nouveau composant :
# Crée un single-file component par défaut
php artisan make:livewire compteur
# Crée un multi-file component (dossier dédié)
php artisan make:livewire compteur --mfc
Islands : isolation intelligente des mises à jour
Les islands représentent LA fonctionnalité phare de Livewire 4. Elles créent des régions isolées qui se rafraîchissent indépendamment du reste du composant.
<?php
// resources/views/components/⚡dashboard.blade.php
use Livewire\Component;
use Livewire\Attributes\Computed;
use App\Models\Order;
use App\Models\User;
new class extends Component {
// Computed property : exécuté uniquement quand l'island se rafraîchit
#[Computed]
public function chiffreAffaires(): float
{
return Order::whereMonth('created_at', now()->month)->sum('total');
}
#[Computed]
public function nouveauxClients(): int
{
return User::whereDate('created_at', '>=', now()->subDays(7))->count();
}
};
?>
<div class="grid grid-cols-2 gap-4">
{{-- Cette island se rafraîchit seule sans toucher au reste --}}
@island(name: 'revenue')
<div class="p-4 bg-white rounded shadow">
<h3>Chiffre d'affaires du mois</h3>
<p class="text-2xl font-bold">{{ number_format($this->chiffreAffaires, 2) }} €</p>
<button wire:click="$refresh">Actualiser</button>
</div>
@endisland
{{-- Island lazy : chargée après le rendu initial --}}
@island(name: 'clients', lazy: true)
<div class="p-4 bg-white rounded shadow">
<h3>Nouveaux clients (7 jours)</h3>
<p class="text-2xl font-bold">{{ $this->nouveauxClients }}</p>
</div>
@endisland
</div>
Quand vous cliquez sur « Actualiser », seule l’island revenue fait une requête serveur et exécute sa computed property. L’island clients reste intacte. Auparavant, il fallait créer des composants enfants séparés pour obtenir cette isolation.
Slots : composition de composants réactifs
Livewire 4 supporte enfin les slots comme les composants Blade. Le contenu injecté reste réactif dans le contexte du parent.
<?php
// resources/views/components/⚡carte.blade.php
use Livewire\Component;
new class extends Component {
public string $titre = '';
};
?>
<div class="border rounded-lg overflow-hidden">
<header class="bg-gray-100 p-4">
<h3>{{ $titre }}</h3>
</header>
<div class="p-4">
{{-- Le slot par défaut --}}
{{ $slot }}
</div>
@isset($footer)
<footer class="bg-gray-50 p-4">
{{ $footer }}
</footer>
@endisset
</div>
Utilisation avec des slots nommés :
<?php
// resources/views/components/⚡liste-produits.blade.php
use Livewire\Component;
use App\Models\Product;
new class extends Component {
public function supprimer(int $id): void
{
Product::destroy($id);
}
};
?>
<div>
@foreach(Product::all() as $produit)
{{-- Le wire:click appelle la méthode du PARENT --}}
<livewire:carte :titre="$produit->nom">
<p>{{ $produit->description }}</p>
<p class="font-bold">{{ $produit->prix }} €</p>
<x-slot:footer>
<button wire:click="supprimer({{ $produit->id }})" class="text-red-600">
Supprimer
</button>
</x-slot:footer>
</livewire:carte>
@endforeach
</div>
Le bouton « Supprimer » appelle supprimer() sur le composant parent liste-produits, pas sur carte.
UI Optimiste : réactivité instantanée
Livewire 4 introduit plusieurs directives pour des interfaces qui réagissent instantanément, sans attendre le serveur.
<?php
// resources/views/components/⚡editeur-article.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public Post $article;
public string $titre = '';
public string $contenu = '';
public function mount(Post $article): void
{
$this->article = $article;
$this->titre = $article->titre;
$this->contenu = $article->contenu;
}
public function sauvegarder(): void
{
$this->article->update([
'titre' => $this->titre,
'contenu' => $this->contenu,
]);
}
};
?>
<div>
{{-- wire:show affiche/masque via CSS, sans requête serveur --}}
<div wire:show="$dirty" class="bg-yellow-100 p-2 rounded mb-4">
⚠️ Modifications non sauvegardées
</div>
<input
wire:model="titre"
type="text"
class="w-full p-2 border rounded"
{{-- wire:bind applique une classe dynamiquement côté client --}}
wire:bind:class="titre.length > 100 && 'border-red-500'"
>
{{-- wire:text met à jour le texte instantanément --}}
<p class="text-sm text-gray-500">
<span wire:text="titre.length">0</span> / 100 caractères
</p>
<textarea wire:model="contenu" rows="10" class="w-full p-2 border rounded mt-4"></textarea>
<button
wire:click="sauvegarder"
class="mt-4 px-4 py-2 bg-blue-600 text-white rounded data-loading:opacity-50"
>
Sauvegarder
</button>
</div>
Les directives clés :
wire:show: toggle CSS instantané (pas de manipulation DOM)wire:text: mise à jour de texte côté clientwire:bind: liaison d’attributs HTML dynamique$dirty: détecte les modifications non sauvegardéesdata-loading: attribut automatique pendant les requêtes
Drag & Drop natif avec wire:sort
Livewire 4 intègre le tri par glisser-déposer sans librairie externe.
<?php
// resources/views/components/⚡liste-taches.blade.php
use Livewire\Component;
use App\Models\Task;
new class extends Component {
public function reordonner(int $taskId, int $nouvellePosition): void
{
$tache = Task::find($taskId);
// Réorganise les positions des autres tâches
Task::where('position', '>=', $nouvellePosition)
->where('id', '!=', $taskId)
->increment('position');
$tache->update(['position' => $nouvellePosition]);
}
};
?>
<ul wire:sort="reordonner" class="space-y-2">
@foreach(Task::orderBy('position')->get() as $tache)
<li
wire:key="{{ $tache->id }}"
wire:sort:item="{{ $tache->id }}"
class="p-4 bg-white rounded shadow flex items-center gap-4"
>
{{-- Handle de drag explicite --}}
<span wire:sort:handle class="cursor-grab">⠿</span>
<span>{{ $tache->titre }}</span>
{{-- wire:sort:ignore empêche le drag sur cet élément --}}
<button wire:sort:ignore wire:click="supprimer({{ $tache->id }})">
🗑️
</button>
</li>
@endforeach
</ul>
La méthode reordonner() reçoit l’ID de l’élément déplacé et sa nouvelle position. Les animations de transition sont gérées automatiquement.
Routing unifié avec Route::livewire()
Livewire 4 introduit une nouvelle macro de routing cohérente :
// routes/web.php
use Illuminate\Support\Facades\Route;
// Nouvelle syntaxe recommandée : référence par nom
Route::livewire('/dashboard', 'pages::dashboard');
Route::livewire('/articles/{article}', 'pages::article.edit');
// L'ancienne syntaxe reste supportée
Route::get('/profil', App\Livewire\Profil::class);
Les namespaces pages:: et layouts:: sont configurés par défaut. Ajoutez vos propres namespaces dans config/livewire.php :
// config/livewire.php
'component_namespaces' => [
'layouts' => resource_path('views/layouts'),
'pages' => resource_path('views/pages'),
'admin' => resource_path('views/admin'), // Namespace personnalisé
],
Migration depuis Livewire 3
La migration reste simple. Les composants class-based fonctionnent toujours. Points d’attention :
- Configuration :
layoutdevientcomponent_layout,lazy_placeholderdevientcomponent_placeholder - wire:model : n’écoute plus les événements des enfants par défaut (ajoutez
.deepsi nécessaire) - wire:transition : utilise désormais l’API View Transitions native (les modifiers
.opacity,.scalesont supprimés) - Volt : si vous utilisez Volt, remplacez simplement
use Livewire\Volt\Componentparuse Livewire\Component
# Convertir un composant class-based en single-file
php artisan livewire:convert nom-du-composant
Bonnes pratiques Livewire 4
- Privilégiez les single-file components pour les composants simples à moyens
- Utilisez les islands pour isoler les parties coûteuses en requêtes
- Combinez
#[Computed]avec les islands pour optimiser les requêtes SQL - Exploitez
wire:showetwire:textavant de recourir à des requêtes serveur - Ajoutez
wire:keydans toutes vos boucles@foreach
Ressources
À propos de Laravel Actu
Développeur passionné par Laravel et son écosystème.
Voir tous les articles