Module 5 – Optimisation UI/UX (loading, feedback)
Niveau 5.1 – Laravel Livewire
wire:loading, wire:target et les modificateurs (.remove, .class, .attr) pour afficher un état « en cours » et désactiver les boutons pendant l’action. En fin de module : skeletons, wire:transition, accessibilité.
Objectif
À la fin de ce module, vous saurez :
- Afficher un indicateur de chargement pendant qu’une action Livewire s’exécute (sauvegarde, suppression, recherche) avec wire:loading.
- Cibler une action précise avec wire:target pour n’afficher le loading que pour un bouton ou une méthode donnée (éviter les messages génériques qui s’affichent partout).
- Utiliser wire:loading.remove, wire:loading.class et wire:loading.attr pour masquer du contenu, appliquer des classes CSS ou modifier des attributs (ex. désactiver un bouton) pendant le chargement.
- Appliquer des bonnes pratiques : skeleton loaders, désactivation des boutons pendant la requête, debounce sur les champs pour limiter la sensation de lenteur.
Pourquoi le feedback utilisateur est important
Quand l’utilisateur clique sur « Enregistrer » ou « Rechercher », une requête AJAX part vers le serveur. Sans feedback visuel, l’utilisateur ne sait pas si l’action a bien été prise en compte ; il peut recliquer (double soumission) ou penser que l’app est figée. Livewire fournit des directives pour afficher un état « en cours » pendant qu’une action est en cours d’exécution côté serveur.
wire:loading : afficher un indicateur pendant l’action
wire:loading affiche son contenu uniquement pendant qu’une requête Livewire est en cours (entre le clic/soumission et la réponse du serveur).
Exemple simple :
<button wire:click="save">Enregistrer</button>
<div wire:loading>
Enregistrement en cours...
</div>
Dès que l’utilisateur clique sur « Enregistrer », le texte « Enregistrement en cours... » apparaît ; il disparaît quand la réponse revient et le DOM est mis à jour.
Si vous avez plusieurs boutons (ex. « Enregistrer » et « Supprimer »), sans précision toute requête Livewire du composant afficherait tous les blocs wire:loading. Pour éviter cela, on cible l’action avec wire:target.
wire:target : cibler une action précise
wire:target="nomMethode" restreint le wire:loading à l’action qui appelle la méthode nomMethode. Ainsi, le loading « Sauvegarde... » ne s’affiche que lors du clic sur le bouton qui déclenche save(), et pas lors d’un delete().
Exemple :
<button wire:click="save">Enregistrer</button>
<div wire:loading wire:target="save">
Sauvegarde...
</div>
<button wire:click="delete">Supprimer</button>
<div wire:loading wire:target="delete">
Suppression...
</div>
- wire:loading wire:target="save" : visible uniquement pendant l’exécution de save().
- wire:loading wire:target="delete" : visible uniquement pendant l’exécution de delete().
Vous pouvez aussi cibler wire:submit (soumission de formulaire) si votre méthode est appelée via wire:submit="save" : wire:target="save" fonctionne de la même façon.
wire:loading.remove et wire:loading.class
Parfois vous préférez masquer un élément pendant le chargement plutôt qu’afficher un message séparé :
- wire:loading.remove : l’élément qui porte cette directive est retiré du DOM pendant le chargement (et réaffiché à la fin).
- wire:loading.class="nom-classe" : la classe CSS est ajoutée à l’élément pendant le chargement (ex. opacity-50 pour griser un bouton, ou pointer-events-none pour désactiver les clics).
Exemples :
<span wire:loading.remove>Contenu normal</span>
<span wire:loading>Chargement...</span>
Pendant la requête : « Contenu normal » disparaît, « Chargement... » apparaît. À la fin, l’inverse.
<button wire:loading.class="opacity-50 cursor-not-allowed" wire:click="submit">
Envoyer
</button>
Pendant submit(), le bouton devient semi-transparent et le curseur indique qu’il est désactivé. Vous pouvez combiner avec wire:target="submit" si vous avez plusieurs boutons.
wire:loading.attr : pour modifier un attribut (ex. disabled) pendant le chargement :
<button wire:loading.attr="disabled" wire:click="save">Enregistrer</button>
Le bouton reçoit disabled pendant l’action, ce qui évite les double-clics.
Exemple complet : formulaire avec feedback
Formulaire avec bouton « Enregistrer » qui affiche « En cours... » et se désactive pendant la sauvegarde.
Vue :
<form wire:submit="save">
<input type="text" wire:model="name">
@error('name') <span class="text-red-600">{{ $message }}</span> @enderror
<button type="submit" wire:loading.attr="disabled" wire:target="save">
<span wire:loading.remove wire:target="save">Enregistrer</span>
<span wire:loading wire:target="save">Enregistrement...</span>
</button>
</form>
- wire:loading.attr="disabled" : le bouton est désactivé pendant save(), ce qui évite les doubles soumissions.
- Le texte du bouton bascule de « Enregistrer » à « Enregistrement... » pendant l’action.
Bonnes pratiques
- Toujours cibler avec wire:target dès qu’il y a plusieurs actions dans le composant, pour que l’utilisateur comprenne quelle action est en cours.
- Désactiver les boutons (wire:loading.attr="disabled" ou wire:loading.class) pendant l’action pour éviter les double-clics et les requêtes en double.
- Pour les listes ou tableaux qui se rechargent (filtres, pagination), un skeleton (wire:loading sur un bloc avec des placeholders gris) améliore la perception de la réactivité.
- Limiter les requêtes : utiliser debounce sur les champs de recherche (module 3) et lazy ou defer quand c’est pertinent, pour que l’interface ne semble pas lente à chaque frappe.
Récap des directives loading :
| Directive | Effet |
|---|---|
| wire:loading | Affiche le contenu de l’élément uniquement pendant une requête. |
| wire:loading wire:target="save" | Idem, mais seulement pendant l’action save. |
| wire:loading.remove | Masque l’élément pendant le chargement. |
| wire:loading.class="opacity-50" | Ajoute la classe CSS pendant le chargement. |
| wire:loading.attr="disabled" | Ajoute l’attribut (ex. disabled) pendant le chargement. |
Sans wire:target, tout wire:loading réagit à n’importe quelle action du composant. Dès qu’il y a plusieurs boutons (Enregistrer, Supprimer, Rechercher), toujours cibler avec wire:target.
Pièges à éviter
- Loading générique : un seul bloc « Chargement... » sans wire:target sur une page avec plusieurs actions : l’utilisateur ne sait pas quelle action est en cours. Toujours wire:target="nomMethode" pour chaque bloc ou bouton.
- Bouton non désactivé : sans wire:loading.attr="disabled" (ou .class pour griser), l’utilisateur peut cliquer plusieurs fois sur « Enregistrer » et déclencher plusieurs soumissions. Désactiver le bouton pendant l’action évite les doublons.
- Texte du bouton qui ne change pas : préférer « Enregistrement... » pendant le chargement (avec wire:loading / wire:loading.remove sur deux spans) pour que l’utilisateur voie clairement que l’action est en cours.
À retenir
- wire:loading : affiche le contenu pendant une requête Livewire. wire:target="nomMethode" pour ne l’afficher que pour cette action (indispensable dès qu’il y a plusieurs boutons).
- wire:loading.remove : masquer l’élément pendant le chargement. wire:loading.class="opacity-50" : griser un bouton. wire:loading.attr="disabled" : désactiver le bouton (évite les double-clics).
- Toujours donner un feedback clair (texte « En cours... » ou état visuel) et éviter les double soumissions en désactivant le bouton ou en ciblant correctement le loading.
Checklist feedback utilisateur
- Chaque bouton d’action (Enregistrer, Supprimer, Rechercher) a un wire:loading associé avec wire:target="nomMethode" pour que l’utilisateur sache quelle action est en cours.
- Le bouton de soumission est désactivé pendant l’action (wire:loading.attr="disabled") pour éviter les double-clics.
- Le texte du bouton ou un message à côté indique l’état « en cours » (ex. « Enregistrement... ») pendant le chargement.
- Sur les listes ou tableaux qui se rechargent (filtres, pagination), un skeleton ou un indicateur générique améliore la perception (optionnel mais recommandé en production).
Approfondissement
- Skeleton loader : au lieu d’afficher « Chargement... » en texte, affichez un bloc avec des placeholders (lignes grises, rectangles) qui imitent la structure du contenu. wire:loading sur ce bloc, wire:loading.remove sur le contenu réel. L’utilisateur perçoit moins d’attente car la mise en page reste stable.
- wire:transition (Livewire 3) : vous pouvez envelopper un wire:loading dans une transition pour un fondu ou un slide. Consultez la doc Livewire pour la syntaxe exacte (entrée/sortie du DOM avec transition CSS).
- Accessibilité : en plus de disabled, ajoutez aria-busy="true" pendant le chargement (via wire:loading.attr="aria-busy" ou une classe qui ajoute l’attribut) pour que les lecteurs d’écran annoncent que l’action est en cours. Annoncer le succès (message flash ou role="alert") après une action importante.
- Performance perçue : sur des actions très rapides (< 200 ms), un indicateur qui clignote peut être plus gênant qu’utile. Vous pouvez n’afficher le loading qu’après un délai (ex. 300 ms) avec un peu de JavaScript/Alpine ; avancé et optionnel.
À retenir
- wire:loading + wire:target="nomMethode" pour cibler l’action ; wire:loading.attr="disabled" pour désactiver le bouton ; wire:loading.remove / .class pour masquer ou styler.
- Toujours cibler dès qu’il y a plusieurs boutons ; toujours désactiver le bouton pendant l’action pour éviter les doubles soumissions.
Dans le prochain module, nous intégrons Eloquent dans les composants : listes paginées, recherche, tri, et optimisation des requêtes (N+1, with(), select()).