Ask AI about this page

Modal Component

Overview

Welcome to this advanced modal component engineered for complete flexibility and designed to be used seamlessly from anywhere in your application.

Installation

Use the fluxtor artisan command to install the modal component easily:

php artisan fluxtor:install modal

then you need to import the ./globals/modals.js in your app.js like so:

// ...
import './globals/modals.js';
// ...

Basic Usage

All you need is to bind a trigger to a modal using a unique id. That's it.

<x-ui.modal.trigger id="basic-modal">
    <x-ui.button>
        Open
    </x-ui.button>
</x-ui.modal.trigger>

<x-ui.modal 
    id="basic-modal"
    heading="Basic Modal"
    description="This is a simple modal example"
>
    <p>Modal content goes here...</p>
</x-ui.modal>

This component is designed to be controlled globally via events while supporting isolated scoped instances, giving you maximum control and flexibility for any UI scenario.

When using modals inside loops or repeated components, it's critical to generate unique modal ids dynamically. Without unique identifiers, a single modal trigger will open all modals sharing the same id on the page, leading to unpredictable and unwanted behavior.

@foreach ($posts as $post)
    <x-ui.modal :id="'edit-post-' . $post->slug">
        <!-- You may put the trigger here, and bind to it the same id -->
    </x-ui.modal>
@endforeach

Usage with Livewire

Just dispatch events like you would do normally:

use Livewire\Component;
 
class CreatePost extends Component
{
    public function update()
    {
        // ...

        $this->dispatch('open-modal', id: 'confirm-update');         
    }
}

See an example of how to build a confirmation modal below

Usage with Blade

<x-ui.button 
    x-on:click="$modal.open('confirm-update')"
>
    Open confirm update modal
</x-ui.button>

<x-ui.button 
    x-on:click="$modal.close('confirm-update')"
>
    Close confirm update modal
</x-ui.button>

<x-ui.button
    x-on:click="$modal.closeAll()"
>
    Close all modals
</x-ui.button>

See a deeper look at modal store below

Positioning

By default, the modal is aligned vertically to the top, but you can also position it at center or bottom.

Center & Bottom Positioning

<div class="flex gap-2 justify-center">
    <x-ui.modal.trigger id="center-position" class="my-4">
        <x-ui.button>
            Center Modal Trigger
        </x-ui.button>
    </x-ui.modal.trigger>
    <x-ui.modal.trigger id="bottom-position" class="my-4">
        <x-ui.button>
            Bottom Modal Trigger
        </x-ui.button>
    </x-ui.modal.trigger>
</div>

<!-- CENTER POSITION MODAL -->
<x-ui.modal
    id="center-position"
    position="center"
    heading="Center Modal"
    description="This modal is centered vertically"
>
    <p>Modal content goes here...</p>
</x-ui.modal> 

<!-- BOTTOM POSITION MODAL -->
<x-ui.modal
    id="bottom-position"
    position="bottom"
    heading="Bottom Modal"
    description="This modal appears at the bottom"
>
    <p>Modal content goes here...</p>
</x-ui.modal> 

Slideover

You can transform the overlay modal into a slideover using the slideover prop:

<x-ui.modal.trigger id="slideover-demo">
    <x-ui.button>
        Open Slideover
    </x-ui.button>
</x-ui.modal.trigger>

<x-ui.modal 
    id="slideover-demo"
    heading="Slideover Modal"
    description="This is a slideover example"
    slideover
>
    <p>Slideover content goes here...</p>
</x-ui.modal>

Backdrop Options

By default, the modal uses a small blur effect as backdrop. In addition, you can choose between transparent and dark variants:

<div class="flex gap-2 justify-center">
    <x-ui.modal.trigger id="transparent-backdrop" class="my-4">
        <x-ui.button>
            Transparent Backdrop
        </x-ui.button>
    </x-ui.modal.trigger>
    <x-ui.modal.trigger id="dark-backdrop" class="my-4">
        <x-ui.button>
            Dark Backdrop
        </x-ui.button>
    </x-ui.modal.trigger>
</div>

<!-- TRANSPARENT BACKDROP MODAL -->
<x-ui.modal
    id="transparent-backdrop"
    backdrop="transparent"
    heading="Transparent Backdrop"
    description="This modal has a transparent backdrop"
>
    <p>You can see the background clearly through the transparent backdrop.</p>
</x-ui.modal> 

<!-- DARK BACKDROP MODAL -->
<x-ui.modal
    id="dark-backdrop"
    backdrop="dark"
    heading="Dark Backdrop"
    description="This modal has a dark backdrop"
>
    <p>The background is darkened for better focus.</p>
</x-ui.modal> 

Need a custom backdrop effect? You own the code, go tweak it for your needs!

Animation Options

Control how your modal appears and disappears with different animation styles:

<!-- Scale Animation (default) -->
<x-ui.modal
    id="scale-animation"
    animation="scale"
    heading="Scale Animation"
>
    <p>Scales from 95% to 100% size</p>
</x-ui.modal>

<!-- Slide Animation -->
<x-ui.modal
    id="slide-animation"
    animation="slide"
    heading="Slide Animation"
>
    <p>Slides in from the right</p>
</x-ui.modal>

<!-- Fade Animation -->
<x-ui.modal
    id="fade-animation"
    animation="fade"
    heading="Fade Animation"
>
    <p>Simple opacity transition</p>
</x-ui.modal>

Modal Sizes

Control the width of your modals with predefined size options:

<!-- Available width options -->
<x-ui.modal width="xs" />     <!-- max-w-xs -->
<x-ui.modal width="sm" />     <!-- max-w-sm (default) -->
<x-ui.modal width="md" />     <!-- max-w-md -->
<x-ui.modal width="lg" />     <!-- max-w-lg -->
<x-ui.modal width="xl" />     <!-- max-w-xl -->
<x-ui.modal width="2xl" />    <!-- max-w-2xl -->
<x-ui.modal width="3xl" />    <!-- max-w-3xl -->
<x-ui.modal width="4xl" />    <!-- max-w-4xl -->
<x-ui.modal width="5xl" />    <!-- max-w-5xl -->
<x-ui.modal width="6xl" />    <!-- max-w-6xl -->
<x-ui.modal width="7xl" />    <!-- max-w-7xl -->
<x-ui.modal width="full" />   <!-- max-w-full -->
<x-ui.modal width="screen-sm" />  <!-- max-w-screen-sm -->
<x-ui.modal width="screen-md" />  <!-- max-w-screen-md -->
<x-ui.modal width="screen-lg" />  <!-- max-w-screen-lg -->
<x-ui.modal width="screen-xl" />  <!-- max-w-screen-xl -->
<x-ui.modal width="screen-2xl" /> <!-- max-w-screen-2xl -->
<x-ui.modal width="screen" />     <!-- fixed inset-0 (full screen) -->

Persistent Modals

Create modals that cannot be closed by clicking away or pressing escape:

<x-ui.modal
    id="persistent-modal"
    persistent
    heading="Persistent Modal"
    description="This modal can only be closed using the close button"
>
    <p>This modal cannot be closed by clicking away or pressing escape.</p>
</x-ui.modal>

Custom Headers and Footers

Custom Footer

Add action buttons and custom footer content:

<x-ui.modal 
    id="custom-footer-modal"
    heading="Modal with Custom Footer"
>
    <p>Modal content goes here...</p>
    
    <x-slot name="footer">
        <div class="flex justify-end space-x-3">
            <x-ui.button x-on:click="$data.close();" variant="outline">
                Cancel
            </x-ui.button>
            <x-ui.button x-on:click="$data.close();" variant="primary">
                Confirm Action
            </x-ui.button>
        </div>
    </x-slot>
</x-ui.modal>

Sticky Headers and Footers

For modals with long content, make headers and footers stick to their positions:

<x-ui.modal
    id="sticky-modal"
    heading="Sticky Header and Footer"
    sticky-header
    sticky-footer
>
    <!-- Long content that scrolls -->
    <div class="space-y-4">
        <!-- Lots of content here -->
    </div>
    
    <x-slot name="footer">
        <!-- Footer buttons stay visible -->
    </x-slot>
</x-ui.modal>

Text Alignment

Control the text alignment within your modal:

<!-- Text alignment options -->
<x-ui.modal alignment="left" />    <!-- text-left (default) -->
<x-ui.modal alignment="center" />  <!-- text-center -->
<x-ui.modal alignment="right" />   <!-- text-right -->
<x-ui.modal alignment="start" />   <!-- text-left (alias) -->
<x-ui.modal alignment="end" />     <!-- text-right (alias) -->

Close Button Control

Customize or hide the close button:

<!-- Hide the close button -->
<x-ui.modal
    close-button="false"
    heading="No Close Button"
>
    <p>Modal without close button</p>
</x-ui.modal>

<!-- With custom close action -->
<x-ui.modal heading="Custom Close">
    <p>Modal content</p>
    
    <x-slot name="footer">
        <x-ui.button x-on:click="$data.close();">
            Custom Close Button
        </x-ui.button>
    </x-slot>
</x-ui.modal>

Event Handling

Custom Event Names

Customize the event names used to open and close modals:

<x-ui.modal
    id="custom-events-modal"
    open-event-name="show-modal"
    close-event-name="hide-modal"
    heading="Custom Events"
>
    <p>This modal uses custom event names</p>
</x-ui.modal>

<!-- Trigger with custom events -->
<x-ui.button x-on:click="$dispatch('show-modal', { id: 'custom-events-modal' })">
    Open with Custom Event
</x-ui.button>

Modal Event Listeners

Listen to modal open/close events:

<div x-data="{}" 
     @modal-opened.window="console.log('Modal opened:', $event.detail.id)"
     @modal-closed.window="console.log('Modal closed:', $event.detail.id)">
    
    <x-ui.modal id="event-demo" heading="Event Demo">
        <p>Check your browser console for events</p>
    </x-ui.modal>
</div>

Accessibility Features

The modal component includes comprehensive accessibility support:

  • ARIA Labels: Properly labeled for screen readers
  • Focus Management: Automatic focus handling and restoration
  • Keyboard Navigation: Full keyboard support including escape key
  • Screen Reader Support: Proper modal role and aria attributes
<x-ui.modal
    id="accessible-modal"
    aria-labelledby="custom-label"
    aria-description="Detailed description for screen readers"
    heading="Accessible Modal"
>
    <p>This modal has enhanced accessibility features</p>
</x-ui.modal>

Advanced Configuration

Disable Specific Close Methods

Fine-tune how users can close your modals:

<x-ui.modal
    id="restricted-close-modal"
    close-by-clicking-away="false"
    close-by-escaping="false"
    heading="Restricted Close"
>
    <p>This modal can only be closed using the close button</p>
</x-ui.modal>

Autofocus Control

Control focus behavior when the modal opens:

<x-ui.modal
    id="no-autofocus-modal"
    autofocus="false"
    heading="No Autofocus"
>
    <p>This modal won't automatically focus on the first input</p>
    <input type="text" placeholder="This won't be focused automatically" />
</x-ui.modal>

Visibility Control

Control initial visibility:

<x-ui.modal
    id="initially-hidden-modal"
    visible="false"
    heading="Initially Hidden"
>
    <p>This modal is initially hidden from the DOM</p>
</x-ui.modal>

Deeper Look at $modal Store

The $modal magic utility in Alpine.js is a global reactive store that manages modals across your entire app. Think of it as a central controller that knows which modals are open and which are closed — and it makes opening and closing modals smooth and consistent everywhere.

modal = Alpine.reactive({
    openModals: new Set(),

    open(id) {
        if (this.openModals.has(id)) return;
        
        this.openModals.add(id);
        window.dispatchEvent(new CustomEvent('open-modal', { detail: { id } }));
    },

    close(id) {
        if(!this.openModals.has(id)) return;

        this.openModals.delete(id);
        window.dispatchEvent(new CustomEvent('close-modal', { detail: { id } }));
    },

    closeAll() {
        this.openModals.forEach(id => {
            this.close(id);
        });
    },
    
    getOpenedModals(){
        return Array.from(Alpine.raw(this.openModals));
    },
    
    isOpen(id) {
        return this.openModals.has(id);
    }
})

Dealing with stacking context

modals had a very z-index, but if you lock the stacking context in a container, and inside that container you put the modal, the modal maybe behind other element like navbars..., that had higher stacking context order

How it Works

  • openModals
    This is a Set holding the IDs of all currently open modals. Using a Set ensures no duplicate IDs, so a modal can't be opened twice by accident.

  • open(id)
    Opens a modal with the given id.

    • First, it checks if this modal is already open. If yes, it does nothing (prevents duplicates).
    • If not open yet, it adds the id to openModals.
    • Then, it dispatches a global browser event open-modal with the modal id in the event's details. This event lets other parts of your app know to show that modal.
  • close(id)
    Closes the modal with the specified id.

    • Checks if the modal is currently open; if not, does nothing.
    • Removes the modal id from the openModals set.
    • Dispatches a close-modal event with the modal id so your app can respond by hiding the modal.
  • closeAll()
    Closes every open modal by iterating over openModals and calling close(id) on each.

  • getOpenedModals()
    Returns an array of all currently open modal IDs.
    Uses Alpine.raw() to get the raw underlying data of the reactive Set, then converts it to a normal array for easy use.

  • isOpen(id)
    Returns true if the modal with this id is open; otherwise false.

Quick Example Usage

$modal.open('login');   // Opens the login modal
$modal.close('login');  // Closes the login modal
console.log($modal.isOpen('login'));  // false after close
$modal.closeAll();      // Close every open modal instantly
console.log($modal.getOpenedModals()); // []

Component Props

Prop Name Type Default Description
id string null Unique identifier for the modal (auto-generated if not provided)
heading string null Modal title text
description string null Modal description text
width string 'sm' Modal width: xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, full, screen-sm, screen-md, screen-lg, screen-xl, screen-2xl, screen
position string 'top' Modal position: top, center, bottom
backdrop string 'blur' Backdrop style: blur, dark, transparent
animation string 'scale' Animation type: scale, slide, fade
slideover boolean false Transform modal into a slideover panel
persistent boolean false Prevent closing by clicking away or escape key
alignment string 'start' Text alignment: start, center, end, left, right
close-button boolean true Show/hide the close button in header
close-by-clicking-away boolean true Allow closing by clicking backdrop
close-by-escaping boolean true Allow closing with escape key
autofocus boolean true Auto-focus first focusable element
sticky-header boolean false Make header sticky when scrolling
sticky-footer boolean false Make footer sticky when scrolling
visible boolean true Initial visibility state
display-classes string 'inline-block' CSS classes for display behavior
open-event-name string 'open-modal' Custom event name for opening
close-event-name string 'close-modal' Custom event name for closing
aria-labelledby string null ARIA labelledby attribute
trigger slot null Inline trigger element
header slot null Custom header content
footer slot null Custom footer content

Trigger Component

The <x-ui.modal.trigger> component provides a convenient way to create modal triggers:

<x-ui.modal.trigger 
    id="my-modal" 
    class="inline-block"
>
    <x-ui.button>Open Modal</x-ui.button>
</x-ui.modal.trigger>

Trigger Props

Prop Name Type Default Description
id string required ID of the modal to trigger
class string '' Additional CSS classes

Best Practices

1. Always Use Unique IDs

<!-- Good -->
@foreach($items as $item)
    <x-ui.modal :id="'edit-item-' . $item->id">
        <!-- Modal content -->
    </x-ui.modal>
@endforeach

<!-- Bad -->
@foreach($items as $item)
    <x-ui.modal id="edit-item">
        <!-- This will cause conflicts -->
    </x-ui.modal>
@endforeach

2. Use Appropriate Sizes

<!-- For simple confirmations -->
<x-ui.modal width="sm" />

<!-- For forms -->
<x-ui.modal width="lg" />

<!-- For complex content -->
<x-ui.modal width="2xl" />

<!-- For mobile-first full-screen experience -->
<x-ui.modal width="screen" />

3. Handle Long Content

<!-- For scrollable content -->
<x-ui.modal 
    width="lg"
    sticky-header
    sticky-footer
>
    <!-- Long content here -->
</x-ui.modal>

4. Accessibility Considerations

<!-- Always provide meaningful headings -->
<x-ui.modal 
    heading="Delete Account"
    description="This action cannot be undone"
>
    <!-- Content -->
</x-ui.modal>

<!-- Use persistent for critical actions -->
<x-ui.modal persistent>
    <!-- Important content that requires user action -->
</x-ui.modal>

5. Event Handling Patterns

<!-- Livewire pattern -->
<x-ui.button wire:click="$dispatch('open-modal', { id: 'confirm-delete' })">
    Delete
</x-ui.button>

<!-- Alpine.js pattern -->
<x-ui.button x-on:click="$modal.open('confirm-delete')">
    Delete
</x-ui.button>

<!-- Custom event pattern -->
<x-ui.button x-on:click="$dispatch('show-confirmation')">
    Delete
</x-ui.button>

This modal component provides the perfect balance of simplicity and power, making it easy to create beautiful, accessible modals while giving you complete control over behavior and appearance.

© FLuxtor Copyright 2024-2025. All rights reserved.