jantinnerezo/livewire-alert
Livewire Alert integrates SweetAlert2 with Laravel Livewire so you can fire toasts, confirmation dialogs, and flash messages straight from your components using a simple fluent API. It handles the JavaScript wiring, keeping all your alert logic in PHP—no front-end code required.
Install and wire up once:
composer require jantinnerezo/livewire-alert
php artisan vendor:publish --tag=livewire-alert-config # optional, for defaults
Place the alert host component once in your layout (typically just before </body>) — without it, every LivewireAlert::...->show() silently does nothing but log to the browser console:
@livewire('livewire-alert')
First use case — a save toast:
use Jantinnerezo\LivewireAlert\Facades\LivewireAlert;
public function save()
{
$this->post->save();
LivewireAlert::title('Saved')->success()->asToast()->show();
}
First new use case (v4.2) — a long-running task without flicker:
LivewireAlert::title('Importing...')->asLoading()->show();
// ... later, from a poll or job callback
LivewireAlert::text('50%')->update();
// ... when done
LivewireAlert::title('Import complete')->success()->update();
LivewireAlert::close();
Look first at the published config file — it holds the global defaults (position, timer, button labels, toast mode) that the fluent API inherits from. Then read the Alertable interface: it is the exact contract your components and any mocks deal with.
Fluent chaining for one-shot notifications. The idiomatic call is a single statement: title()->text()->success()->position()->timer()->show(). Anything you don't specify comes from config. Use ->asToast() for non-blocking feedback, ->asQuestion() + confirm/dismiss handlers for destructive actions.
Question / confirmation flow. Pair an ask-alert with a Livewire listener so the actual work happens server-side after confirmation:
LivewireAlert::title('Delete this post?')
->asQuestion()
->confirmButton('Delete')
->onConfirm('destroy')
->show();
Live progress with asLoading() + wire:poll + update() (the headline v4.2 pattern). Open a spinner-preset alert, let a poll tick read state, and mutate the same alert in place:
public function start()
{
$this->batchId = Bus::batch([...])->dispatch()->id;
LivewireAlert::title('Processing...')->asLoading()->show();
}
public function checkProgress()
{
$batch = Bus::findBatch($this->batchId);
if ($batch->finished()) {
LivewireAlert::title('Done')->text("{$batch->processedJobs()} jobs")->update();
LivewireAlert::close();
return;
}
LivewireAlert::title('Processing...')
->text($batch->progress().'%')
->update();
}
Keep the polling method tiny and never show() again inside it — the whole point is that the alert is already open.
Two update() dialects — pick deliberately.
LivewireAlert::update(['title' => 'Retrying...', 'timer' => null, 'showConfirmButton' => false]);
Integration with job/batch callbacks. Since update() and close() are stateless, you can drive them from anywhere a Livewire request originates — a poll, a #[On] listener, or after a queued notification. The alert is addressed implicitly ("whatever is currently visible"), so the cleanest architecture is one long-lived alert per component instance, updated by key rather than re-created.
Phased multi-step flows. Move from spinner → progress → result in a single alert lifetime:
LivewireAlert::title('Uploading...')->asLoading()->show(); // step 1
LivewireAlert::title('Validating')->text('3 of 5')->update(); // step 2
LivewireAlert::title('All set')->success()->update(); // step 3
update() cannot unset things. Because it only emits explicitly-set options (no config merge), there is no way to remove a previously set timer, showConfirmButton, or icon fluently. Fall back to LivewireAlert::update([...]) with the explicit values.update() and close() do nothing but a console.warn when no alert is visible. If your progress UI "never updates," first check you called show()/asLoading()->show() in the same page lifecycle — a full-page wire:navigate or redirect kills the SweetAlert instance.asLoading() disables backdrop clicks (and is non-dismissable). If a job throws, the user is locked out of the UI until they reload. Always wrap in try/finally and guarantee a terminal update() or close():try { $this->doWork(); }
catch (Throwable $e) {
LivewireAlert::title('Failed')->text($e->getMessage())->error()->update();
return;
}
LivewireAlert::close();
update() then close() in the same request can race the browser round-trip; the last state usually wins but the intermediate update may never paint. If you want the user to see the final state, update() in one request and close() on the next poll, or use ->timer() and let SweetAlert dismiss itself.asLoading() overrides your config. It forces spinner + non-dismissable + backdrop-click-disabled, so position, timer, and button settings from config still apply, but anything that would let the user close it is suppressed by design.update() sends a small options payload, but the containing Livewire request is a full round-trip. Don't poll faster than ~1s, and put the progress method on a dedicated component or wire:poll.2s so you aren't re-rendering a heavy view every tick.update()-driven progress, toasts for fire-and-forget messages.Alertable additions are additive but not free. New methods on the interface mean hand-rolled fakes/mocks implementing it will need asLoading(), close(), and update() added — check your test doubles after upgrading.@livewire('livewire-alert') in the layout of this page, (2) does the browser console show a console.warn (proves the bridge loaded and no alert was open), (3) is the component actually re-rendering on poll, (4) is a redirect or wire:navigate tearing down the DOM.How can I help you explore Laravel packages today?