This package is inspired by wire-elements/modal, forked and rebuilt from scratch to provide full support for Livewire v3 and Tailwind 4. It provides a powerful Livewire component that gives you a modal system that supports multiple child modals while maintaining state.
- 🚀 Fully compatible with Livewire v3
- 🎨 Styled with Tailwind 4
- 🔄 Maintains component state between modal interactions
- 📦 Support for nested/stacked modals
- 🛡️ Secure handling of data
- ⚡ Optimized performance
- đź”§ Highly customizable
You can install the package via composer:
composer require cloudstudio/laravel-livewire-modal
After installing the package, you need to include the modal component in your blade layout file:
<livewire:modal />
To properly configure Tailwind 4 with this package, add these lines to your app.css
file:
@import '../../vendor/cloudstudio/laravel-livewire-modal/dist/modal.css';
Then run:
yarn build
This ensures Tailwind can properly scan and generate the necessary styles for the modal components.
Create a Livewire component that extends the LivewireModal
class:
<?php
namespace App\Livewire;
use Cloudstudio\Modal\LivewireModal;
use Illuminate\View\View;
class CreateUser extends LivewireModal
{
public $name = '';
public $email = '';
protected $rules = [
'name' => 'required|min:3',
'email' => 'required|email',
];
public function create()
{
$this->validate();
$user = User::create([
'name' => $this->name,
'email' => $this->email,
'password' => bcrypt('password'),
]);
$this->closeModal();
// Optionally emit events when the modal is closed
$this->dispatch('userCreated', $user->id);
}
public function render(): View
{
return view('livewire.create-user');
}
}
To open a modal from a Livewire component or a Blade view:
<!-- From a button using onclick -->
<button onclick="Livewire.dispatch('openModal', { component: 'create-user' })">Create User</button>
<!-- From a Livewire component using wire:click -->
<button wire:click="$dispatch('openModal', { component: 'create-user' })">Create User</button>
<!-- With arguments -->
<button onclick="Livewire.dispatch('openModal', { component: 'edit-user', arguments: { user: {{ $user->id }} } })">
Edit User
</button>
You can pass arguments to your modal when opening it:
public User $user;
public function mount(User $user)
{
$this->user = $user;
$this->name = $user->name;
$this->email = $user->email;
}
You can dispatch events when closing a modal:
public function update()
{
$this->validate();
$this->user->update([
'name' => $this->name,
'email' => $this->email,
]);
$this->closeModalWithEvents([
'userUpdated', // Event name
'userUpdated' => $this->user->id, // Event with data
UserOverview::class => 'userModified', // Component event
UserOverview::class => ['userModified', [$this->user->id]], // Component event with parameters
]);
}
You can change the width of the modal by overriding the modalMaxWidth
method:
/**
* Supported sizes: 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'
*/
public static function modalMaxWidth(): string
{
return 'xl';
}
To display the modal as a flyout:
public static function modalFlyout(): bool
{
return true;
}
To change the position of the flyout.
Available positions are right
, left
, and bottom
.
Default is right
:
public static function modalFlyoutPosition(): string
{
return 'left';
}
To prevent the modal from closing when the escape key is pressed:
public static function closeModalOnEscape(): bool
{
return false;
}
To prevent the modal from closing when clicking outside:
public static function closeModalOnClickAway(): bool
{
return false;
}
By default, pressing escape closes all modals. To change this behavior:
public static function closeModalOnEscapeIsForceful(): bool
{
return false;
}
To dispatch an event when the modal is closed:
public static function dispatchCloseEvent(): bool
{
return true;
}
To destroy the component state when a modal is closed:
public static function destroyOnClose(): bool
{
return true;
}
You can prevent the modal from closing based on its state:
@script
<script>
$wire.on('closingModalOnEscape', data => {
if ($wire.isDirty && !confirm('{{ __('You have unsaved changes. Are you sure you want to close this dialog?') }}')) {
data.closing = false;
}
});
$wire.on('closingModalOnClickAway', data => {
if ($wire.isDirty && !confirm('{{ __('You have unsaved changes. Are you sure you want to close this dialog?') }}')) {
data.closing = false;
}
});
</script>
@endscript
For nested modal workflows where you want to skip returning to certain previous modals:
public function delete()
{
// Delete logic here
// Skip the previous modal and close with events
$this->skipPreviousModal()->closeModalWithEvents([
TeamOverview::class => 'teamDeleted'
]);
// Or skip multiple previous modals
// $this->skipPreviousModals(2)->closeModal();
// Optionally destroy the skipped modals' state
// $this->destroySkippedModals();
}
Publish the configuration file:
php artisan vendor:publish --tag=livewire-modal-config
This will create a livewire-modal.php
config file with the following options:
<?php
return [
/*
|--------------------------------------------------------------------------
| Modal Component Defaults
|--------------------------------------------------------------------------
|
| Configure the default properties for a modal component.
|
| Supported modal_max_width
| 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'
*/
'component_defaults' => [
'modal_max_width' => '2xl',
'display_as_flyout' => false,
'flyout_position' => 'right',
'close_modal_on_click_away' => true,
'close_modal_on_escape' => true,
'close_modal_on_escape_is_forceful' => true,
'dispatch_close_event' => false,
'destroy_on_close' => false,
],
];
Remember to validate all data passed to your Livewire components. Since Livewire stores this information on the client-side, it can be manipulated. Use Laravel's Gate facade and other authorization mechanisms to secure your application.
composer test
Please see CHANGELOG for more information on what has changed recently.
The MIT License (MIT). Please see License File for more information.