Installation:
composer require edulazaro/wiremodal
php artisan vendor:publish --tag=wiremodal-assets
Add the CSS/JS to your layout:
<link rel="stylesheet" href="{{ asset('vendor/wiremodal/css/wiremodal.css') }}">
<script src="{{ asset('vendor/wiremodal/js/wiremodal.js') }}" defer></script>
Or import via Vite:
@import "../../vendor/edulazaro/wiremodal/resources/css/wiremodal.css";
import '../../vendor/edulazaro/wiremodal/resources/js/wiremodal.js';
First Modal: Define a modal in your Blade view:
<x-wiremodal name="simple-modal" title="Hello">
<x-slot:body>
<p>This is a basic modal.</p>
</x-slot:body>
</x-wiremodal>
Trigger It: Use Livewire to open it:
$this->openModal('simple-modal');
Or via JavaScript:
Wiremodal.open('simple-modal');
Create a confirmation modal for a delete action:
<x-wiremodal name="confirm-delete" title="Confirm" size="sm">
<x-slot:body>
<p>Are you sure you want to delete this item?</p>
</x-slot:body>
<x-slot:footer>
<button type="button" data-wm-dismiss>Cancel</button>
<button type="button" onclick="Wiremodal.close('confirm-delete', 'confirmed')">Delete</button>
</x-slot:footer>
</x-wiremodal>
Trigger it in Livewire:
$this->openModal('confirm-delete');
Handle the result in JavaScript:
const result = await Wiremodal.open('confirm-delete');
if (result === 'confirmed') {
// Proceed with deletion
}
Modal Lifecycle:
Use openModal()/closeModal() in Livewire methods:
public function deleteItem()
{
$this->openModal('confirm-delete');
$result = $this->dispatch('modal-result')->wait();
if ($result === 'confirmed') {
// Delete logic
}
}
Passing Data:
$this->openModal('edit-task', ['id' => 123, 'title' => 'Update Task']);
Access in Alpine:
<x-wiremodal name="edit-task" x-data="{ payload: {} }"
@wiremodal:opened.window="payload = $event.detail.data">
<!-- Use payload.title -->
</x-wiremodal>
Dynamic Modals: Register modals dynamically in Livewire:
public function mount()
{
$this->modals = [
'edit-task' => ['title' => 'Edit Task', 'size' => 'md'],
];
}
Modal Promises:
Use await for async flows:
const userChoice = await Wiremodal.open('confirm-action');
if (userChoice === 'yes') {
// Proceed
}
Event Listeners: Listen for modal events globally:
window.addEventListener('wiremodal:opened', (e) => {
if (e.detail.name === 'edit-task') {
console.log('Modal opened with data:', e.detail.data);
}
});
Alpine Reactive Modals: Combine with Alpine for reactive UI:
<x-wiremodal name="form-modal" x-data="{ form: {} }"
@wiremodal:opened.window="form = $event.detail.data || {}">
<!-- Form fields bound to `form` -->
</x-wiremodal>
Apply Themes:
Set theme on <html> or <body>:
<html data-wire-theme="claude" data-wire-theme-mode="dark">
Custom Themes: Override CSS variables:
[data-wire-theme="brand"] {
--wire-bg: #f5f5f5;
--wire-accent: #4a6fa5;
}
Modal-Specific Styles: Target modal classes:
.wm-modal[data-wm-name="edit-task"] .wm-title {
color: #ff6b35;
}
Modal Stacking: Open multiple modals sequentially:
await Wiremodal.open('modal-1');
await Wiremodal.open('modal-2');
Modal Forms:
Use wire:submit inside modals:
<x-wiremodal name="submit-form">
<x-slot:body>
<form wire:submit="saveForm">
<!-- Form fields -->
</form>
</x-slot:body>
</x-wiremodal>
Conditional Modals: Show/hide modals based on Livewire state:
@if($showModal)
<x-wiremodal name="alert" title="Notice">
<x-slot:body>
<p>{{ $message }}</p>
</x-slot:body>
</x-wiremodal>
@endif
Event Conflicts:
wiremodal:opened listeners are scoped correctly to avoid memory leaks.Modal Z-Index:
.wm-modal {
z-index: 1000 !important;
}
Livewire + Alpine Sync:
x-init runs after Livewire mounts:
<div x-init="$nextTick(() => { /* Alpine init */ })">
Persistent Modals:
persistent modals can block user interaction. Use sparingly.Modal State:
Check data-wm-state attribute for debugging:
console.log(document.querySelector('.wm-modal').dataset.wmState);
Event Inspection: Log events to verify payloads:
window.addEventListener('wiremodal:opened', (e) => {
console.log('Opened:', e.detail);
});
Livewire Dispatch: Verify events are dispatched:
$this->dispatch('open-wiremodal', name: 'test', data: ['key' => 'value']);
Check browser console for dispatched events.
Reusable Modal Components: Create a Blade component for common modals:
<!-- resources/views/components/modal.blade.php -->
<x-wiremodal name="{{ $name }}" {{ $attributes }}>
{{ $slot }}
</x-wiremodal>
Usage:
<x-modal name="confirm" size="sm">
<x-slot:body>...</x-slot:body>
</x-modal>
Modal Data Validation: Validate payloads in Alpine:
<x-wiremodal name="edit-user" x-data="{
payload: {},
validate() {
if (!this.payload.name) {
alert('Name is required!');
return false;
}
return true;
}
}">
<!-- Form with validation -->
</x-wiremodal>
Dark Mode Toggle: Dynamically switch themes:
function toggleTheme() {
const body = document.body;
body.dataset.wireThemeMode = body.dataset.wireThemeMode === 'dark' ? 'light' : 'dark';
}
Accessibility:
aria-live to modals for screen readers:
<x-wiremodal name="alert" aria-live="assertive">
data-wm-dismiss buttons have visible labels.Performance:
<x-wiremodal name="heavy-modal" x-show="showModal" x-transition>
size="xs" for lightweight modals.Custom Events: Extend with your own events:
Wiremodal.on('custom-event', (name, data) => {
// Handle custom logic
});
Modal Factory: Create a helper class for modal management:
How can I help you explore Laravel packages today?