https://github.com/mustafakhaleddev/filament-modal-notifications
Render any Filament notification as a blocking modal
https://github.com/mustafakhaleddev/filament-modal-notifications
Last synced: 2 months ago
JSON representation
Render any Filament notification as a blocking modal
- Host: GitHub
- URL: https://github.com/mustafakhaleddev/filament-modal-notifications
- Owner: mustafakhaleddev
- Created: 2026-04-23T15:08:26.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-04-24T10:39:07.000Z (2 months ago)
- Last Synced: 2026-04-28T21:31:50.879Z (2 months ago)
- Language: PHP
- Size: 5.86 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Filament Modal Notifications
Render any Filament notification as a blocking modal by chaining one method: `->asModal()`. Multiple modal notifications fired in the same request are queued one at a time — the user dismisses one and the next slides in, no stacking.
Everything else you already know about Filament notifications (title, body, icon, color, actions, `danger()`/`success()`/`warning()`/`info()`) carries over unchanged.
## Requirements
- PHP 8.2+
- Laravel 11+ / 13
- Filament 4 or 5
## Features
- **One-line opt-in** — chain `->asModal()` onto any `Notification::make()` call. No new classes to learn.
- **Queued** — multiple modal notifications show one at a time, dismissal advances to the next.
- **Auto-added Close button** — if the developer didn't declare actions, the modal gets a "Close" button in its footer so it's always dismissible.
- **Respects your actions** — if you declare `->actions([...])`, those render in the footer instead.
- **Conditional** — `->asModal($isCritical)` is a no-op when the condition is false, so the notification sends as a regular toast.
- **Independent of the toast tray** — modal notifications never briefly flash as toasts; they use a dedicated session key and Livewire event.
- **Plugin-level defaults** — set the default modal width, closable behavior, and close button label per panel.
## Installation
```bash
composer require wezlo/filament-modal-notifications
```
Register the plugin on your panel so the Livewire component mounts at the page footer:
```php
use Wezlo\FilamentModalNotifications\FilamentModalNotificationsPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentModalNotificationsPlugin::make(),
]);
}
```
Optionally publish the config:
```bash
php artisan vendor:publish --tag=filament-modal-notifications-config
```
## Quick Start
```php
use Filament\Notifications\Notification;
Notification::make()
->title('Changes not saved')
->body('Your unsaved edits will be lost if you leave this page.')
->danger()
->asModal()
->send();
```
That's it. On the next Livewire cycle, a modal opens with the title, body, and a default Close button. Regular toasts still work exactly as before — only notifications with `->asModal()` go through the modal pipeline.
## Usage
### With custom actions
The notification's actions render in the modal footer:
```php
use Filament\Actions\Action;
use Filament\Notifications\Notification;
Notification::make()
->title('Delete this order?')
->body('This cannot be undone.')
->warning()
->actions([
Action::make('cancel')->label('Cancel')->color('gray')->close(),
Action::make('delete')->label('Delete')->color('danger')
->action(fn () => $this->record->delete())
->close(),
])
->asModal()
->send();
```
When any action's `->close()` is invoked (or the user clicks X / Esc / clicks outside if closable), the queue advances to the next pending modal notification.
### Conditional modal
`->asModal(false)` leaves the notification as a normal toast:
```php
Notification::make()
->title($wasCritical ? 'Error' : 'Saved')
->asModal($wasCritical)
->send();
```
### Queued — multiple in one request
```php
Notification::make()->title('Step 1 complete')->asModal()->send();
Notification::make()->title('Step 2 complete')->asModal()->send();
Notification::make()->title('Step 3 complete')->asModal()->send();
```
The user sees one modal at a time in FIFO order. Each dismissal advances to the next. When the queue is empty the modal closes.
### Panel-level defaults
```php
use Filament\Support\Enums\Width;
use Wezlo\FilamentModalNotifications\FilamentModalNotificationsPlugin;
->plugins([
FilamentModalNotificationsPlugin::make()
->defaultWidth(Width::Large)
->defaultClosable(false) // block Esc / click-away / X — require an explicit action
->defaultCloseButtonLabel('Dismiss'),
])
```
| Method | Type | Default | Description |
|---|---|---|---|
| `defaultWidth(Width\|string)` | enum/string | `md` | Modal width (`sm`, `md`, `lg`, `xl`, `2xl`, …, `screen`) |
| `defaultClosable(bool)` | bool | `true` | Whether the modal can be dismissed via X, Esc, or click-away |
| `defaultCloseButtonLabel(string)` | string | `Close` | Label for the auto-added Close action |
## Default Config File
```php
// config/filament-modal-notifications.php
return [
'default_width' => 'md',
'default_closable' => true,
'default_close_label' => 'Close',
];
```
## How It Works
- **`->asModal()` is a macro** registered on `Filament\Notifications\Notification` in the service provider. It returns a `Wezlo\FilamentModalNotifications\ModalNotification` — a subclass that overrides `send()` to push into a dedicated session key (`filament.modal-notifications`) instead of the default `filament.notifications`.
- **A custom dehydrate hook** dispatches the `modalNotificationsSent` Livewire event when the modal session key has pending notifications (same pattern Filament uses for its toast tray).
- **A Livewire component** (`Wezlo\FilamentModalNotifications\Livewire\ModalNotifications`) mounts at `PanelsRenderHook::BODY_END`. It listens for `modalNotificationsSent`, drains the session key into an in-component queue, and renders the first pending notification through ``. When the user dismisses the modal (X, Esc, click-away, or any action chained with `->close()`), the component advances to the next queued notification.
- **No toast flash** — because modal notifications use a separate session key, the default toast tray never sees them.
## License
MIT