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

  1. Keep modals focused - One purpose per modal
  2. Provide clear actions - Always include cancel/close option
  3. Use loading states - Show feedback during async operations
  4. Confirm destructive actions - Always confirm deletes
  5. Don't nest modals - Avoid modal-in-modal patterns
  6. Use drawers for forms - Better UX for multi-field forms
  7. Close on success - Auto-close after successful action

Next Steps

  • Buttons - Modal action buttons
  • Inputs - Form inputs for modal forms
  • Alerts - Feedback after modal actions
/