pleshkovpa/toasts-livewire
Toast notifications for Laravel Livewire apps: dispatch lightweight success, error, and info messages that render inside your Livewire components with minimal setup. Adds simple, customizable flash-style feedback for form submissions, actions, and other in-app events.
Prerequisites. The package renders Bootstrap toasts inside Livewire components, so before anything else your layout must load Bootstrap 5 CSS and the Bootstrap JS bundle (the toast dismissal/timer behavior is Bootstrap JS, not the package):
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
...
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
Install and publish.
composer require pleshkovpa/toasts-livewire
php artisan vendor:publish # pick the package's config and view tags from the list
Publishing is the important step: the config file holds the duration default, and the Blade view is where the toast markup, classes, and variant mapping live. Read both published files before writing any calling code — with thin public docs, the published view is the API reference.
Mount the toast container once. Put the package's view/component in your main layout, outside the Livewire component you're toasting from, usually bottom-right:
<body>
{{ $slot }}
@include('toasts-livewire::toasts') {{-- confirm the published namespace --}}
</body>
If instead the package exposes a Livewire component, mount it as <livewire:toasts />. Check the published view path to see which it is.
First use case. Fire a toast from any component method after a successful action:
class EditPost extends Component
{
public function save(): void
{
$this->post->save();
$this->toast('Post saved.', 'success');
}
}
Verify the argument order and variant strings against the published view on first install — ('message', 'variant') vs ('success', 'message') is exactly the kind of thing that won't be documented and will silently render wrong. One dd() in the view or a read of the component's method signature settles it in a minute.
Feedback on mutations, not on renders. The natural rhythm is: mutate, then toast the outcome. Keep the toast call as the last statement in the action, after the DB work has committed, so a toast never announces success that a subsequent exception reverses.
public function delete(): void
{
$this->authorize('delete', $this->post);
$this->post->delete();
$this->toast('Post deleted.', 'success');
$this->redirect(route('posts.index'), navigate: true);
}
Branch the variant on the outcome, not on the code path. The four variants are just Bootstrap contextual classes. Pick a house convention early and enforce it with a small wrapper trait, so a codebase review can grep for toast('...', 'error') and find every error surface:
trait ToastsFeedback
{
protected function ok(string $m): void { $this->toast($m, 'success'); }
protected function fail(string $m): void { $this->toast($m, 'error'); }
protected function warn(string $m): void { $this->toast($m, 'warning'); }
protected function note(string $m): void { $this->toast($m, 'info'); }
}
This also insulates you from the package's exact signature — if it changes, one trait changes.
Don't double-report validation. Livewire already renders per-field errors under each input. Toasting "The form has errors" on top of that is noise. Reserve error toasts for failures that aren't reflected in field errors: authorization failures, domain-rule violations caught from a service, third-party API failures.
public function save(): void
{
$this->validate();
try {
$this->order->submit();
} catch (InsufficientStockException $e) {
$this->fail('Not enough stock to submit this order.');
return;
}
$this->ok('Order submitted.');
}
Bridging from non-Livewire code. The API is component-scoped, so controllers, jobs, and listeners can't call it. Two workable bridges:
ToastHost component listens on a Laravel event or broadcast channel and fans messages into toasts. Use this for queue-job completion (broadcast on a private channel, host listens via Echo). Note that Livewire 3's getListeners() / #[On] is the hook, and the host component must stay mounted.Consistent durations per severity. Auto-dismiss is fine for success and info; error and warning are the ones users need to actually read. Set the package duration default for the common case in config, and pass a longer duration explicitly for the severe variants if the API accepts a third argument — otherwise encode the distinction in the published view by reading the variant and overriding the delay attribute there. That keeps the decision in one place instead of scattered across call sites.
Testing. These toasts are server-dispatched, so assert at the Livewire boundary rather than trying to assert on DOM that Bootstrap will animate away:
Livewire::test(EditPost::class)
->call('save')
->assertSee('Post saved.'); // or assertDispatched/assertSet if the package exposes state
Add one integration test per app that mounts the real toast container in a layout and hits it through a full HTTP request. That's the boundary where a package update or a layout refactor will break you first.
Bootstrap JS is a hard dependency. CSS-only setups render an unstyled block that never dismisses; a missing bundle script is the #1 "the package is broken" false alarm. Confirm your layout's bundle loads before filing anything upstream.
Auto-dismiss timers vs. Livewire morphing. A toast lives in the same DOM tree Livewire patches. If a re-render touches the toast container, Bootstrap's timer can reset (toast never dies) or the element can be swapped out mid-animation. If you see either, wrap the container in wire:ignore and let the component manage append/remove itself, or render toasts into a portal div that Livewire never re-renders. This is the most likely real-world bug you'll hit and it's cheap to preempt.
Publishable view drift. Once your team customizes the published Blade view, vendor:publish --force on a package update silently overwrites it. Treat the published views as application code: commit them, and diff before re-publishing. Same story for the config.
Verify the API against vendor/, not the README. Last release is February 2023 — early in the Livewire 3 cycle. Pin the version in composer.json, and on first install read src/ (it's small) to confirm: the toast() signature, variant strings, whether it accepts a duration per call, and whether it's a trait, a base component, or a macro. Two minutes of reading prevents a day of debugging a wrong-variant info appearing as error.
Check the render escaping. Look at how the message is printed in the published view — {{ }} vs {!! !!}. If it's unescaped, never pass user-supplied strings into a toast (e.g. an echoed form value) without escaping them yourself. Given the package's age and size, verify rather than assume.
Accessibility. Bootstrap toasts are not announced by default. Add role="status" aria-live="polite" to the container in the published view for success/info, and consider aria-live="assertive" for errors. Do this once in the view, not per call site.
Stacking and z-index. Toasts placed in the layout can render under Bootstrap modals and offcanvas. If your app uses modals heavily, bump the toast container's z-index above --bs-modal-zindex in the published view, or mount a second container inside the modal. Also cap concurrent toasts — repeated failed clicks on a slow save will stack ten identical errors; coalesce by message key or drop duplicates before calling toast().
No persistence across full redirects. With navigate: true (SPA-style), the layout persists and toasts survive. With a plain redirect(), the component and its toast state are gone — use the session-flash bridge described above. Knowing which redirect style each action uses is what determines whether your "saved" message ever appears.
Extension points to plan around. There are only two: the config (duration defaults) and the Blade view (markup, classes, variant mapping). Everything else is the $this->toast() call. That's a narrow surface, which is good for stability and bad for customization — if you need icons, titles, or action buttons inside a toast, you're editing the published view, and that edit is now your maintenance burden on every package update. Decide up front whether you're customizing the view or accepting stock Bootstrap markup.
How can I help you explore Laravel packages today?