Mastering Livewire 4 Loading States: Creating Smooth User Experiences
User experience matters. When a user clicks a button, they need immediate visual feedback that something is happening. Livewire 4 makes this trivially easy with loading states.
The Basic Pattern
The wire:loading directive shows or hides elements while a Livewire action is in progress:
<button wire:click="savePost">
Save
</button>
<span wire:loading>
Saving your changes...
</span>
But we can do much better than this basic example.
Advanced Loading Patterns
Disabling Actions During Loading
Prevent duplicate submissions by disabling the button while an action runs:
<button
wire:click="savePost"
wire:loading.attr="disabled"
wire:loading.class="opacity-50 cursor-not-allowed"
>
Save
</button>
Showing Different Content
Sometimes you want to completely change what's displayed:
<div wire:loading.remove>
<p>{{ $message }}</p>
</div>
<div wire:loading>
<div class="animate-spin">Loading...</div>
</div>
Delayed Loading Indicators
Show the loading spinner only if the action takes longer than expected:
<span wire:loading.delay>
This is taking a while...
</span>
By default, this waits 200ms. Customize with wire:loading.delay.500:
<span wire:loading.delay.500ms>
Still working on this...
</span>
Targeting Specific Actions
When your component has multiple actions, use targets to show loading states for specific ones:
<button wire:click="savePost" wire:loading.remove.delay="savePost">
Save Post
</button>
<button wire:click="deletePost" wire:loading.remove.delay="deletePost">
Delete
</button>
<span wire:loading.delay="savePost">Saving...</span>
<span wire:loading.delay="deletePost">Deleting...</span>
Combining with Transitions
Use Alpine.js transitions for smooth fade-ins and fade-outs:
<div wire:loading.class="opacity-0" wire:loading.class.remove="opacity-100">
<button wire:click="submit">Submit</button>
</div>
<div
wire:loading
wire:loading.class="opacity-100"
wire:loading.class.remove="opacity-0"
class="opacity-0 transition-opacity"
>
<span>Processing your request...</span>
</div>
Real-World Example: Form Submission
Here's a complete example of a form with proper loading states:
<form wire:submit="submitForm">
<input type="email" wire:model="email" required />
<button
type="submit"
wire:loading.attr="disabled"
wire:loading.class="opacity-50"
class="transition-opacity"
>
<span wire:loading.remove>Submit</span>
<span wire:loading>
<i class="fas fa-spinner fa-spin"></i> Submitting...
</span>
</button>
</form>
@error('email')
<p class="text-red-500">{{ $message }}</p>
@enderror
Performance Notes
Loading states are entirely client-side. They don't add any server overhead and provide instant visual feedback, making your app feel snappier than it actually is (and often making it actually faster because users don't get frustrated and click multiple times).
Conclusion
Proper loading states are the difference between an app that feels sluggish and one that feels responsive. Livewire makes implementing them so easy that there's no excuse not to use them everywhere. Your users will appreciate the polish, and you'll write less JavaScript.
You Might Also Like
Building Reactive Forms with Livewire 4: A Complete Guide
Learn how to create dynamic, reactive forms using Livewire 4 that feel responsive and modern without writing a single line of JavaScript.
Advanced Laravel Query Optimization: From N+1 to Perfection
Master the art of writing efficient Laravel queries. Learn about eager loading, query optimization, and how to debug N+1 problems like a pro.
Laravel Eloquent Model Scopes: Write Less, Express More
Discover how to use local and global scopes to write cleaner, more reusable queries. Keep your code DRY and your intentions clear.