Skip to content
Go back

Replacing a Custom Notification System with Flux::toast()

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/:

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:

FeatureCustom implementationFlux toast
Variants (success, warning, danger)Hardcoded green onlyBuilt-in
Auto-dismiss durationFixed at 2.5sConfigurable (default 5s)
Stacking multiple toastsNoYes
Position optionsFixedConfigurable
Custom Blade/Alpine code~80 lines across 2 files0 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:

  1. assertDispatched('toast-show') — direct replacement, tests the right thing, matches how the Flux community tests toasts.
  2. Drop the assertion entirely — simpler, but loses coverage that the user gets feedback.
  3. Mock Flux::toast() — over-engineered since Flux isn’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

The app now has error and warning toast variants available for the first time, with no additional work needed.


Back to top ↑