nawasara/toaster
Lightweight Alpine-powered toast notifications for Laravel and Livewire. Add one component to your layout and trigger success/error/warning/info toasts from JavaScript, Livewire, or session flash. Configurable position, optional progress bar, dark-mode aware.
nawasara/ui package. Dispatch events from Livewire:
use Nawasara\Ui\Livewire\Concerns\HasBrowserToast;
class MyComponent extends Component {
use HasBrowserToast;
public function save() {
// ...
$this->toast('success', 'Saved in real-time!');
}
}
The toaster listens for toast events via Alpine.js:
// Auto-handled by <x-nawasara-toaster::script />
session('toast') on page load and renders it automatically. Override defaults:
session()->flash('toast', [
'type' => 'warning',
'message' => 'Custom flash message',
'position' => 'bottom-center',
]);
status) to toasts:
// app/Http/Middleware/RedirectFlashToToast.php
public function handle($request, Closure $next) {
if ($request->session()->has('status')) {
$request->session()->flash('toast', [
'type' => 'info',
'message' => $request->session()->get('status'),
]);
$request->session()->forget('status');
}
return $next($request);
}
document.addEventListener('alpine:init', () => {
Alpine.store('toaster', {
extend: {
defaultDuration: 7000,
defaultPosition: 'top-center',
},
});
});
session()->flash('toast', [
'type' => 'info',
'message' => '<strong>User</strong> created: <span class="font-mono">@entertainly</span>',
]);
The toaster escapes HTML by default; use message_html for raw HTML:
session()->flash('toast', [
'type' => 'info',
'message_html' => '<strong>User</strong> created: <span class="font-mono">@entertainly</span>',
]);
question) by extending the Alpine store:
Alpine.store('toaster', {
extend: {
types: {
question: {
icon: '❓',
class: 'bg-blue-500 text-white',
},
},
},
});
Then use it:
window.Toast.question('Confirm action?');
protected $rules = ['email' => 'required|email'];
public function updated($property) {
$this->validateOnly($property);
if ($this->validate($property)) {
$this->toast('success', 'Email is valid!');
} else {
$this->toast('error', 'Invalid email.');
}
}
public function withValidator($validator) {
$validator->after(function ($validator) {
if ($validator->errors()->any()) {
session()->flash('toast', [
'type' => 'error',
'message' => 'Please fix errors below.',
]);
}
});
}
// Test flash integration
$response = $this->post('/submit', []);
$response->assertRedirect();
$this->assertSessionHas('toast');
// In a Livewire test
Livewire::test('MyComponent')
->call('save')
->assertToast('success', 'Saved!');
(Note: Requires custom assertions or Alpine store inspection.)// Debug toasts in browser console
console.log(Alpine.store('toaster').toasts);
Flash Message Overwrite:
session()->flash('toast') calls overwrite each other.session()->push('toast.queue', ['type' => 'info', 'message' => 'New item']);
(Requires custom Alpine store logic to handle queues.)Livewire Event Race Conditions:
HasBrowserToast) may fire out of order or duplicate.// In Alpine script
let lastToastId = 0;
window.addEventListener('toast', (e) => {
if (e.detail.id !== lastToastId) {
lastToastId = e.detail.id;
// Show toast
}
});
Alpine.js Conflicts:
Alpine.store('toaster', { ... });
(Avoid global Alpine.store('toaster') if another package uses it.)Dark Mode Quirks:
Alpine.store('toaster', {
extend: {
darkModeClass: 'dark:bg-gray-800', // Override
},
});
Session Timeout:
session()->putNow() or ensure session lifetime is sufficient.Livewire Component Isolation:
wire:ignore carefully.Inspect Alpine Store:
console.log(Alpine.store('toaster'));
toasts, config, and events for state.Check for JavaScript Errors:
<x-nawasara-toaster::script /> loads before triggers (e.g., window.Toast).window.Alpine).Livewire Event Debugging:
window.addEventListener('toast', (e) => console.log(e.detail));
Flash Data Inspection:
dd(session()->all());
CSS Conflicts:
<x-nawasara-toaster::toaster class="my-toaster" />
.my-toaster .toast { /* Custom styles */ }
Default Position:
position attribute on <x-nawasara-toaster::toaster /> overrides the global default.top-left, top-center, top-right, center, bottom-left, bottom-center, bottom-right, bottom-start, bottom-end.Duration Handling:
duration: 0 disables auto-dismiss.null may cause unexpected behavior (validate inputs).Progress Bar:
Alpine.store('toaster', {
extend: {
showProgress: false,
},
});
Mobile Responsiveness:
md:top-0). Override breakpoints if needed:
Alpine.store('toaster', {
extend: {
mobileBreakpoint: 'sm', // Default: 'md'
},
});
Alpine.store('toaster', {
extend: {
toastTemplate: `
<div x-data="{ open: true }" class="toast">
<div class="message" x-text="message"></div>
<button @click="open = false">Dismiss</button>
<slot></slot
How can I help you explore Laravel packages today?