Back to blog

Mastering Livewire 4 Loading States: Creating Smooth User Experiences

Livewire UX Loading States User Experience

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