I had two custom notification components in my Laravel app — one for full notifications with a title and message, and a smaller one for quick “Saved” confirmations. They worked fine. They’d been in the codebase for months, used across 17 files. But they were entirely hand-rolled when the UI library I was already using (Flux) ships a toast system that does the same thing, better.
This is the story of ripping them out and replacing them with Flux::toast().
Table of contents
Open Table of contents
What I had before
Two Blade components sitting in resources/views/components/modals/:
notification.blade.php— listened for ashow-notificationwindow event via Alpine.js, displayed a title and message with a green checkmark, auto-dismissed after 2.5 seconds.mini-notification.blade.php— same pattern, but smaller and title-only. Used for autosave confirmations.
Both were included in the app layout with Livewire’s @persist directive so they survived page navigations:
@persist('notification')
<x-modals.notification/>
@endpersist
@persist('mini-notification')
<x-modals.mini-notification/>
@endpersist
Every Livewire component that needed to show feedback dispatched a custom event:
$this->dispatch(
'show-notification',
title: 'Idea created',
message: 'Your idea has been created successfully'
);
And in Blade templates with Alpine.js handlers:
$dispatch('show-notification', {
title: 'Copied!',
message: 'Paste the prompt in your preferred AI tool.'
});
It worked. But it was custom code I had to maintain, with hardcoded green-checkmark styling, no support for error or warning variants, and no stacking when multiple toasts fired in quick succession.
What Flux provides out of the box
Flux’s toast component handles all of this with zero custom code:
| Feature | Custom implementation | Flux toast |
|---|---|---|
| Variants (success, warning, danger) | Hardcoded green only | Built-in |
| Auto-dismiss duration | Fixed at 2.5s | Configurable (default 5s) |
| Stacking multiple toasts | No | Yes |
| Position options | Fixed | Configurable |
| Custom Blade/Alpine code | ~80 lines across 2 files | 0 lines |
In the layout, the two @persist blocks collapse to one:
@persist('toast')
<flux:toast />
@endpersist
The migration
PHP: $this->dispatch() becomes Flux::toast()
Every Livewire dispatch call changed to a Flux::toast() call. Add use Flux\Flux; to the imports, then:
Before:
$this->dispatch(
'show-notification',
title: 'Experiment saved!',
message: 'Your experiment has been saved successfully'
);
After:
Flux::toast(
heading: 'Experiment saved!',
text: 'Your experiment has been saved successfully',
variant: 'success'
);
For the mini-notification (title-only, used for autosave), it’s even simpler:
Before:
$this->dispatch(
'show-mini-notification',
title: 'Saved',
);
After:
Flux::toast(text: 'Saved', variant: 'success');
Blade/Alpine: $dispatch() becomes $flux.toast()
In Alpine.js x-on:click handlers:
Before:
$dispatch('show-notification', {
title: 'Copied!',
message: 'Paste the prompt in your preferred AI tool.'
});
After:
$flux.toast({
heading: 'Copied!',
text: 'Paste the prompt in your preferred AI tool.',
variant: 'success'
});
Tests: new event name
This was the part I had to think about. My test used assertDispatched('show-notification') to verify that deleting an idea showed feedback to the user. Since Flux::toast() is no longer a custom event, what do you assert against?
Under the hood, Flux::toast() calls $this->dispatch('toast-show', ...) (source). So the fix is straightforward:
Before:
Livewire::test(Table::class)
->call('deleteItem', $idea->id, 'idea')
->assertDispatched('show-notification');
After:
Livewire::test(Table::class)
->call('deleteItem', $idea->id, 'idea')
->assertDispatched('toast-show');
I considered three options for this:
assertDispatched('toast-show')— direct replacement, tests the right thing, matches how the Flux community tests toasts.- Drop the assertion entirely — simpler, but loses coverage that the user gets feedback.
- Mock
Flux::toast()— over-engineered sinceFluxisn’t a standard Laravel facade.
Option 1 wins. The toast-show event name is a stable public contract — Caleb Porzio can’t change it without breaking every Flux app in production.
The question I asked
When planning the migration, I wanted to make sure I wasn’t missing anything:
The app uses a custom show-notification event throughout — not Flux’s built-in toast. This is the established pattern across 17 files, I’d like to plan out migrating to $flux.toast()
The answer was a clean mapping between old and new APIs, a file-by-file list of every dispatch call, and a verification step at the end to grep the entire codebase for any remaining references. The verification step caught four stale compiled view cache files that still contained the old event name — easy to miss if you don’t check.
What would Taylor Otwell do?
Use the framework’s built-in component. Laravel’s philosophy is don’t reinvent wheels the framework already provides. Flux is the official Livewire UI library, and Flux::toast() is its canonical notification API. Rolling your own notification when Flux::toast() exists is the equivalent of writing a custom auth system when Jetstream is right there.
Final tally
- 17 files changed (12 PHP, 3 Blade, 1 layout, 1 test)
- 2 files deleted (the custom notification components)
- 4 stale cache files cleaned up
- 0 lines of custom notification code remaining
The app now has error and warning toast variants available for the first time, with no additional work needed.