UI components

Modals

Modals

Modals display focused content and actions without leaving the current page. Laravolt v7 uses Alpine.js and Preline UI for accessible, keyboard-navigable modals.

Basic modal

Blade
<div x-data="{ open: false }">
{{-- Trigger button --}}
<button
@click="open = true"
type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700"
>
Open modal
</button>
{{-- Modal --}}
<div
x-show="open"
x-cloak
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 overflow-y-auto"
>
{{-- Backdrop --}}
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
class="fixed inset-0 bg-gray-900 bg-opacity-50 dark:bg-opacity-80"
></div>
{{-- Modal content --}}
<div class="flex min-h-full items-center justify-center p-4">
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
@click.away="open = false"
class="relative w-full max-w-lg bg-white border shadow-sm rounded-xl dark:bg-neutral-900 dark:border-neutral-700"
>
{{-- Header --}}
<div class="flex justify-between items-center py-3 px-4 border-b dark:border-neutral-700">
<h3 class="font-bold text-gray-800 dark:text-white">
Modal title
</h3>
<button
@click="open = false"
type="button"
class="inline-flex items-center justify-center size-7 text-sm font-semibold rounded-full border border-transparent text-gray-800 hover:bg-gray-100 dark:text-white dark:hover:bg-neutral-700"
>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{{-- Body --}}
<div class="p-4 overflow-y-auto">
<p class="text-gray-800 dark:text-neutral-400">
Modal content goes here.
</p>
</div>
{{-- Footer --}}
<div class="flex justify-end items-center gap-x-2 py-3 px-4 border-t dark:border-neutral-700">
<button
@click="open = false"
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 hover:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700"
>
Cancel
</button>
<button
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700"
>
Save changes
</button>
</div>
</div>
</div>
</div>
</div>

Confirmation dialog

Confirm destructive actions:

Blade
<div x-data="{ confirmDelete: false }">
<button
@click="confirmDelete = true"
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-red-600 text-white hover:bg-red-700"
>
Delete user
</button>
<div
x-show="confirmDelete"
x-cloak
@keydown.escape.window="confirmDelete = false"
class="fixed inset-0 z-50 overflow-y-auto"
>
<div
x-show="confirmDelete"
@click="confirmDelete = false"
class="fixed inset-0 bg-gray-900 bg-opacity-50"
></div>
<div class="flex min-h-full items-center justify-center p-4">
<div
x-show="confirmDelete"
@click.away="confirmDelete = false"
class="relative w-full max-w-md bg-white border shadow-sm rounded-xl dark:bg-neutral-900 dark:border-neutral-700"
>
<div class="p-4 sm:p-7">
<div class="text-center">
<div class="mb-4 inline-flex justify-center items-center size-12 rounded-full border-4 border-red-50 bg-red-100 text-red-500 dark:bg-red-700 dark:border-red-600 dark:text-red-100">
<svg class="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 class="mb-2 text-xl font-bold text-gray-800 dark:text-neutral-200">
Delete user?
</h3>
<p class="text-gray-500 dark:text-neutral-500">
This action cannot be undone. All data associated with this user will be permanently deleted.
</p>
<div class="mt-6 flex justify-center gap-x-4">
<button
@click="confirmDelete = false"
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 hover:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700"
>
Cancel
</button>
<form method="POST" action="{{ route('users.destroy', $user) }}">
@csrf
@method('DELETE')
<button
type="submit"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-red-600 text-white hover:bg-red-700"
>
Delete user
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

Form modal

Submit forms inside modals:

Blade
<div x-data="{ showForm: false }">
<button
@click="showForm = true"
type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700"
>
Create user
</button>
<div
x-show="showForm"
x-cloak
@keydown.escape.window="showForm = false"
class="fixed inset-0 z-50 overflow-y-auto"
>
<div
x-show="showForm"
@click="showForm = false"
class="fixed inset-0 bg-gray-900 bg-opacity-50"
></div>
<div class="flex min-h-full items-center justify-center p-4">
<div
x-show="showForm"
@click.away="showForm = false"
class="relative w-full max-w-lg bg-white border shadow-sm rounded-xl dark:bg-neutral-900 dark:border-neutral-700"
>
<div class="flex justify-between items-center py-3 px-4 border-b dark:border-neutral-700">
<h3 class="font-bold text-gray-800 dark:text-white">
Create new user
</h3>
<button
@click="showForm = false"
type="button"
class="inline-flex items-center justify-center size-7 text-sm font-semibold rounded-full border border-transparent text-gray-800 hover:bg-gray-100 dark:text-white dark:hover:bg-neutral-700"
>
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="p-4">
{!! PrelineForm::open('users.store')->post() !!}
<div class="space-y-4">
{!! PrelineForm::text('name')->label('Full name')->required() !!}
{!! PrelineForm::email('email')->label('Email')->required() !!}
{!! PrelineForm::password('password')->label('Password')->required() !!}
</div>
<div class="mt-6 flex justify-end gap-x-2">
<button
@click="showForm = false"
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 hover:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700"
>
Cancel
</button>
{!! PrelineForm::submit('Create user')->primary() !!}
</div>
{!! PrelineForm::close() !!}
</div>
</div>
</div>
</div>
</div>

Livewire integration

Use Livewire for dynamic modal content:

Blade
{{-- Livewire component --}}
<div>
<button
wire:click="$set('showModal', true)"
type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700"
>
Edit user
</button>
@if($showModal)
<div
x-data="{ open: @entangle('showModal') }"
x-show="open"
x-cloak
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 overflow-y-auto"
>
<div
x-show="open"
@click="open = false"
class="fixed inset-0 bg-gray-900 bg-opacity-50"
></div>
<div class="flex min-h-full items-center justify-center p-4">
<div
x-show="open"
@click.away="open = false"
class="relative w-full max-w-lg bg-white border shadow-sm rounded-xl dark:bg-neutral-900 dark:border-neutral-700"
>
<div class="p-4">
<h3 class="text-lg font-bold text-gray-800 dark:text-white mb-4">
Edit user
</h3>
<form wire:submit="save">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Name</label>
<input wire:model="name" type="text" class="py-3 px-4 block w-full border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400">
@error('name') <span class="text-sm text-red-600">{{ $message }}</span> @enderror
</div>
</div>
<div class="mt-6 flex justify-end gap-x-2">
<button
wire:click="$set('showModal', false)"
type="button"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 hover:bg-gray-50"
>
Cancel
</button>
<button
type="submit"
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700"
>
Save changes
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endif
</div>

Accessibility

Modals must be keyboard-navigable and screen-reader friendly:

  • Escape key: Close modal with @keydown.escape.window="open = false"
  • Click outside: Close with @click.away="open = false"
  • Focus trap: Keep focus inside modal while open
  • ARIA attributes: Add role="dialog" and aria-labelledby
Blade
<div
role="dialog"
aria-labelledby="modal-title"
aria-modal="true"
x-show="open"
class="..."
>
<h3 id="modal-title" class="...">
Modal title
</h3>
...
</div>

Common pitfalls

Missing x-cloak

Add x-cloak to prevent flash of unstyled content:

Blade
<div x-show="open" x-cloak class="...">

Z-index conflicts

Ensure modal z-index is higher than other elements:

Blade
<div class="fixed inset-0 z-50 ...">

Form submission in modals

Use Livewire or AJAX to avoid page reload:

Blade
<form wire:submit="save">
{{-- Form fields --}}
</form>

Verification commands

Test modals:

Bash
# Open modal → verify backdrop appears
# Press Escape → verify modal closes
# Click outside → verify modal closes
# Submit form → verify validation errors display
# Check dark mode → verify dark: classes apply
# Accessibility check
# Tab through modal → verify focus stays inside
# Screen reader → verify role="dialog" and aria-* attributes
Previous
Cards