Modal Components
Dialog and overlay components for confirmations, forms, and focused user interactions.
Modal Components
Modal components create focused dialog overlays for confirmations, forms, and other interactions that require user attention. Built with Alpine.js for smooth animations and accessibility.
Available Modal Components
| Component | Purpose |
|---|---|
<x-modal.modal> |
Base modal component |
<x-modals.confirm-delete> |
Delete confirmation dialog |
<x-modals.error-message> |
Error display modal |
<x-modals.test-email> |
Email testing dialog |
<x-modals.create-backup> |
Backup creation dialog |
<x-modals.ai-command> |
AI command modal |
<x-modals.license-activation> |
License activation dialog |
Modal Component
The base modal component with header, body, and footer slots. Controlled via Alpine.js state.
Slots
| Slot | Description |
|---|---|
header |
Modal header/title area |
default |
Modal body content |
footer |
Modal footer with actions |
Variables
| Variable | Type | Default | Description |
|---|---|---|---|
$headerClass |
string | '' |
Additional header classes |
$bodyClass |
string | '' |
Additional body classes |
$footerClass |
string | '' |
Additional footer classes |
Required Alpine.js Setup
The modal requires an open variable in the parent Alpine.js component:
<div x-data="{ open: false }">
{{-- Trigger button --}}
<button @click="open = true" class="btn-primary">
Open Modal
</button>
{{-- Modal --}}
<x-modal.modal>
<x-slot name="header">Modal Title</x-slot>
<p>Modal content goes here.</p>
<x-slot name="footer">
<x-buttons.button @click="open = false" variant="secondary">
Cancel
</x-buttons.button>
<x-buttons.button variant="primary">
Confirm
</x-buttons.button>
</x-slot>
</x-modal.modal>
</div>
Basic Modal
<div x-data="{ showModal: false }">
<x-buttons.button @click="showModal = true" variant="primary">
Open Modal
</x-buttons.button>
<div x-data="{ open: showModal }" x-effect="open = showModal">
<x-modal.modal>
<x-slot name="header">Welcome</x-slot>
<p class="text-gray-600 dark:text-gray-300">
Welcome to our application!
</p>
<x-slot name="footer">
<x-buttons.button @click="showModal = false" variant="primary">
Got it!
</x-buttons.button>
</x-slot>
</x-modal.modal>
</div>
</div>
Modal with Form
<div x-data="{ editModal: false, selectedUser: null }">
{{-- Trigger --}}
<x-buttons.button @click="editModal = true; selectedUser = {{ $user->id }}">
Edit User
</x-buttons.button>
{{-- Modal --}}
<div x-data="{ open: editModal }" x-effect="open = editModal">
<x-modal.modal>
<x-slot name="header">{{ __('Edit User') }}</x-slot>
<form wire:submit="updateUser">
<div class="space-y-4">
<x-inputs.input
label="Name"
name="name"
wire:model="name"
/>
<x-inputs.input
label="Email"
name="email"
wire:model="email"
/>
</div>
<x-slot name="footer">
<x-buttons.button @click="editModal = false" variant="secondary">
Cancel
</x-buttons.button>
<x-buttons.button type="submit" variant="primary">
Save Changes
</x-buttons.button>
</x-slot>
</form>
</x-modal.modal>
</div>
</div>
Without Header (Close Button Only)
<div x-data="{ open: false }">
<x-buttons.button @click="open = true">Show Info</x-buttons.button>
<x-modal.modal>
{{-- No header slot = automatic close button in top-right --}}
<div class="text-center py-4">
<iconify-icon icon="lucide:check-circle" class="text-success text-6xl mb-4"></iconify-icon>
<h3 class="text-lg font-semibold">Success!</h3>
<p class="text-gray-600">Your action was completed successfully.</p>
</div>
</x-modal.modal>
</div>
Real Example from Codebase
{{-- Livewire component with modal --}}
<div x-data="{ confirmDelete: false, deleteId: null }">
@foreach($items as $item)
<tr>
<td>{{ $item->name }}</td>
<td>
<x-buttons.button
variant="danger"
@click="confirmDelete = true; deleteId = {{ $item->id }}"
icon="lucide:trash-2"
>
Delete
</x-buttons.button>
</td>
</tr>
@endforeach
{{-- Delete confirmation modal --}}
<div x-data="{ open: confirmDelete }" x-effect="open = confirmDelete">
<x-modal.modal>
<x-slot name="header">{{ __('Confirm Delete') }}</x-slot>
<p class="text-gray-600 dark:text-gray-300">
{{ __('Are you sure you want to delete this item? This action cannot be undone.') }}
</p>
<x-slot name="footer">
<x-buttons.button @click="confirmDelete = false" variant="secondary">
{{ __('Cancel') }}
</x-buttons.button>
<x-buttons.button
@click="$wire.delete(deleteId); confirmDelete = false"
variant="danger"
>
{{ __('Delete') }}
</x-buttons.button>
</x-slot>
</x-modal.modal>
</div>
</div>
Confirm Delete Modal
Pre-built confirmation dialog specifically for delete operations.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string | 'Confirm Delete' |
Modal title |
message |
string | 'Are you sure?' |
Confirmation message |
confirmLabel |
string | 'Delete' |
Confirm button label |
cancelLabel |
string | 'Cancel' |
Cancel button label |
Basic Usage
<div x-data="{ showDeleteModal: false }">
<x-buttons.button @click="showDeleteModal = true" variant="danger">
Delete
</x-buttons.button>
<x-modals.confirm-delete
x-show="showDeleteModal"
title="Delete User?"
message="This will permanently delete the user and all their data."
@confirm="$wire.delete(); showDeleteModal = false"
@cancel="showDeleteModal = false"
/>
</div>
Real Example
{{-- In a Livewire component --}}
<div x-data="{ deleteModal: @entangle('showDeleteModal') }">
<x-buttons.button @click="deleteModal = true" variant="danger" icon="lucide:trash-2">
{{ __('Delete Account') }}
</x-buttons.button>
<x-modals.confirm-delete
title="{{ __('Delete Account') }}"
message="{{ __('All your data will be permanently deleted. This cannot be undone.') }}"
@confirm="$wire.deleteAccount()"
@cancel="deleteModal = false"
/>
</div>
Error Message Modal
Displays error details in a modal dialog.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string | 'Error' |
Modal title |
message |
string | required | Error message |
details |
string | null |
Additional error details |
Basic Usage
<x-modals.error-message
title="Upload Failed"
message="The file could not be uploaded."
details="Maximum file size is 10MB."
/>
With Livewire
@if($errorMessage)
<x-modals.error-message
title="{{ __('Operation Failed') }}"
:message="$errorMessage"
@close="$wire.set('errorMessage', null)"
/>
@endif
Test Email Modal
Modal for testing email configuration.
Basic Usage
<x-modals.test-email />
This modal is typically triggered from email settings pages to allow administrators to send test emails.
Create Backup Modal
Modal for initiating system backup.
Basic Usage
<x-modals.create-backup />
Used in system administration pages to start backup creation with progress feedback.
AI Command Modal
Modal for AI-powered command input.
Basic Usage
<x-modals.ai-command />
Provides a command palette-style interface for AI interactions.
Drawer Component
A slide-out panel from the side of the screen, useful for forms, settings, and detailed content.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
btn |
string | null |
Trigger button text |
isOpen |
boolean | false |
Initial open state |
title |
string | null |
Drawer title |
btnClass |
string | 'btn-primary' |
Button CSS class |
btnIcon |
string | 'lucide:plus-circle' |
Button icon |
width |
string | 'sm:w-120' |
Drawer width |
drawerId |
string | auto-generated | Unique drawer ID |
headerBtn |
string | null |
Secondary header button |
footerBtn |
string | null |
Footer action button |
footerText |
string | null |
Footer helper text |
Slots
| Slot | Description |
|---|---|
default |
Drawer body content |
footer |
Custom footer content |
Basic Usage
<x-drawer btn="Add User" title="Create New User">
<form wire:submit="createUser">
<div class="space-y-4">
<x-inputs.input label="Name" name="name" wire:model="name" />
<x-inputs.input label="Email" name="email" wire:model="email" />
</div>
</form>
</x-drawer>
With Custom Footer
<x-drawer btn="Settings" title="User Settings">
<div class="space-y-4">
<x-inputs.checkbox label="Enable notifications" name="notifications" />
<x-inputs.checkbox label="Dark mode" name="dark_mode" />
</div>
<x-slot name="footer">
<div class="flex justify-end gap-2">
<x-buttons.button @click="close()" variant="secondary">
Cancel
</x-buttons.button>
<x-buttons.button wire:click="save" variant="primary">
Save Settings
</x-buttons.button>
</div>
</x-slot>
</x-drawer>
With Footer Button and Text
<x-drawer
btn="New Item"
title="Add Item"
footerBtn="Create Item"
footerText="Items are saved automatically"
>
<x-inputs.input label="Item Name" name="item_name" />
</x-drawer>
Opening Programmatically
<x-drawer drawerId="settings-drawer" title="Settings">
<p>Settings content...</p>
</x-drawer>
{{-- Open from anywhere via JavaScript --}}
<button onclick="openDrawer('settings-drawer')">
Open Settings
</button>
Real Example from Codebase
{{-- Create resource drawer --}}
<x-drawer
btn="{{ __('Add Permission') }}"
title="{{ __('Create Permission') }}"
btnIcon="lucide:plus"
>
<form wire:submit="createPermission">
<div class="space-y-4">
<x-inputs.input
label="{{ __('Permission Name') }}"
name="name"
wire:model="newPermissionName"
placeholder="e.g., manage-users"
required
/>
<x-inputs.textarea
label="{{ __('Description') }}"
name="description"
wire:model="newPermissionDescription"
rows="3"
/>
</div>
<x-slot name="footer">
<div class="flex justify-end gap-2">
<x-buttons.button @click="close()" variant="secondary">
{{ __('Cancel') }}
</x-buttons.button>
<x-buttons.button type="submit" variant="primary" loadingTarget="createPermission">
{{ __('Create Permission') }}
</x-buttons.button>
</div>
</x-slot>
</form>
</x-drawer>
Modal vs Drawer Comparison
| Feature | Modal | Drawer |
|---|---|---|
| Position | Center of screen | Slides from side |
| Use case | Confirmations, alerts | Forms, settings, details |
| Size | Fixed width | Scrollable height |
| Focus trap | Yes | Yes |
| Escape to close | Yes | Yes |
| Click outside | Closes | Closes |
When to Use Modal
- Confirmation dialogs
- Quick alerts
- Small forms (1-3 fields)
- Actions requiring immediate attention
When to Use Drawer
- Longer forms
- Detail views
- Settings panels
- Sidebar-style content
Accessibility Features
Both modal and drawer components include:
- Focus trap - Tab navigation stays within the dialog
- Escape key - Press Escape to close
- Click outside - Close by clicking backdrop
- ARIA attributes - Proper dialog roles
- Focus return - Focus returns to trigger on close
Best Practices
- Keep modals focused - One purpose per modal
- Provide clear actions - Always include cancel/close option
- Use loading states - Show feedback during async operations
- Confirm destructive actions - Always confirm deletes
- Don't nest modals - Avoid modal-in-modal patterns
- Use drawers for forms - Better UX for multi-field forms
- Close on success - Auto-close after successful action