bastinald/laravel-livewire-loader
Laravel Livewire Loader adds a simple loading indicator for Livewire requests, so users see feedback while components update. Drop it in and get a spinner/overlay during network activity without writing custom JavaScript. Lightweight and easy to configure for Laravel apps using Livewire.
Install the package:
composer require bastinald/laravel-livewire-loader
The package is intentionally minimal — a blade view (the spinner markup) plus a small script that hooks into Livewire's JS event bus. No migrations, no required config, no service bindings to wire up.
To get a first-use case working:
<x-livewire-loader /> into your primary layout, just before </body> and after @livewireScripts. One per page.wire:click, form submit, wire:model debounce). The overlay appears during the request and hides on completion.php artisan vendor:publish --tag=livewire-loader-views (verify the tag name in LivewireLoaderServiceProvider).The first real use case is an app where per-component wire:loading spinners are hidden inside scrollable containers (tables, cards) or otherwise invisible. A single global overlay makes "something is happening" obvious without touching any component markup.
Where to look in the repo:
src/LivewireLoaderServiceProvider.php — what it registers (View::addNamespace, Blade::component, view path).resources/views/*.blade.php — spinner markup and the inline JS that attaches to livewire:load / message.sent / message.processed.composer.json — Livewire version constraint, which tells you which hook API it targets.Register once, at layout level. Put <x-livewire-loader /> in resources/views/layouts/app.blade.php (or equivalent). Placing it inside page views means it stacks on SPA navigation or double-renders. Place it outside any Livewire component root so DOM morphing doesn't clobber it.
Pair global with local. Keep the global spinner for page-level feedback ("a request is running") and use wire:loading.delay.long or wire:target on individual buttons for per-action indicators. They're complementary — the global one covers cases where the triggering element is off-screen.
Fork the view for per-context styling. This package is small enough that copying the published blade and editing the CSS is the intended customization path. Common forks: a translucent backdrop for polling, a bottom-right toast-style spinner for background saves, a brand-colored SVG.
Handle Livewire 3 navigation. If you're on Livewire 3 with wire:navigate, the package's message hooks won't fire during soft navigation. Add a small listener for livewire:navigating / livewire:navigated (or the generic DOMContentLoaded equivalent) so the spinner also covers route transitions.
Keep Alpine state separate. Don't drive the loader from Alpine x-data inside a component. Use the package's hooks for network state; use Alpine for local UI ("row is in edit mode"). Mixing them causes the spinner to fire on pure client-side state changes.
Combine with optimistic UI carefully. If you're doing optimistic updates (Alpine mutates, then Livewire syncs), the spinner will appear after the UI already updated, which reads as a second, redundant action. Either skip the loader for those actions or short-circuit it with a class check.
Fast-request flicker. Every wire:click fires message.sent, including 20ms ones. The spinner flashes on almost every interaction. Add a minimum-visible duration in the published view (e.g. setTimeout of 150–250ms before showing the overlay, and don't hide until it's been up at least that long). This is the single biggest quality-of-life fix.
Overlay swallows clicks. A viewport-covering overlay with opacity or display mishandled will block the whole page. Make sure the "hidden" state is display: none (not opacity: 0 or visibility: hidden with layout intact), or set pointer-events: none on the overlay and only re-enable on the spinner element.
Stuck spinner on errors. If a Livewire action throws a validation or server error, message.processed may fire but message.failed may not run the "hide" branch — or vice versa depending on version. Hook the hide logic to both message.processed and message.failed, wrapped so it always removes the overlay.
Z-index vs modals. Full-viewport overlays sit below Bootstrap modals (z-index: 1055) and most Tailwind modal layers. Expect the spinner to be invisible when a modal is open, or, worse, to be visible but positioned under an overlay that intercepts clicks. Bump the z-index or scope the spinner to the modal container.
Livewire version drift. The package predates Livewire 3 event renames. If you're on v3, verify livewire:load still fires (some hooks were renamed to morph, commit, request). If the hooks silently do nothing, port the JS inline — it's ~20 lines and easier to maintain than a fork of the whole package.
Polling floods the spinner. wire:poll and lazy-loaded components trigger the same message.sent hook. If background polling is part of your app, either disable the loader on those components (via a data-no-loader attribute or by skipping wire:target) or the spinner will pulse every few seconds.
Extension point is the view, not a config file. With roughly one blade file and one small script, almost all customization is vendor:publish + edit. There is no meaningful config surface — don't go looking for one.
Maintenance signal matters. Twenty-five stars and low activity means: read the source before you depend on it, be prepared to inline the hook logic in your layout, and don't expect Livewire 4 fixes. In a new project, consider copying the ~30 lines of JS + blade into your own layout — you lose almost nothing and gain control over the exact hook behavior.
How can I help you explore Laravel packages today?