Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Livewire Modal Laravel Package

elegantly/livewire-modal

Elegantly/livewire-modal is a simple, elegant modal package for Laravel Livewire. Build reusable modal components, trigger them from Livewire actions, and add dialogs to existing views with minimal setup. Clean Blade integration makes building and customizing dialogs quick and easy.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish.

    composer require elegantly/livewire-modal
    php artisan vendor:publish --tag="modal-config"
    php artisan vendor:publish --tag="modal-views"   # only if you plan to restyle
    
  2. Mount the host once. Add the package's modal host to your main layout, just before </body> and outside anything that gets swapped by wire:navigate:

    {{-- resources/views/layouts/app.blade.php --}}
    @livewireScripts
    <livewire:modal />
    
  3. Look here first: the published config (stack/limits/teleport target) and the base modal class the package ships. Everything else — opening, closing, stacking — flows through the Modal facade and the host component. Skim those two files before reading any docs beyond the README; the surface area is small.

  4. First use case: edit-form modal from an index page.

    // app/Livewire/Posts/Index.php
    use Elegantly\Modal\Modal;
    use App\Livewire\Posts\Edit;
    
    public function edit(int $postId): void
    {
        Modal::open(Edit::class, ['postId' => $postId]);
    }
    
    <button wire:click="edit({{ $post->id }})">Edit</button>
    
    // app/Livewire/Posts/Edit.php — a normal Livewire component
    class Edit extends Component
    {
        public Post $post;
        public array $form = [];
    
        public function mount(int $postId): void
        {
            $this->post = Post::findOrFail($postId);
            $this->form = $this->post->only(['title', 'body']);
        }
    
        public function save(): void
        {
            $this->validate(['form.title' => 'required', 'form.body' => 'required']);
            $this->post->update($this->form);
            Modal::close();
            $this->dispatch('post-saved')->to(Index::class);
        }
    
        public function render() { return view('livewire.posts.edit'); }
    }
    

    That's the whole loop: open with a class + params, render a normal Livewire component inside, close when done, notify the parent by event.

Implementation Patterns

Trigger from Blade without a Livewire method. For static pages, bind the open call to a plain wire:click on a tiny action component, or use the package's Blade trigger/@livewire helper if you don't want a full Livewire parent. Keep triggers as Livewire methods when you can — it keeps authorization in one place.

Confirmation dialogs. Make a generic Confirm component that takes title, message, confirmLabel, and an action identifier, then dispatches a named event back to the caller. Centralizing this means "are you sure?" never grows its own bespoke component per feature.

Parent refresh after mutation. Have the modal dispatch a scoped event (->to(Index::class)) rather than a global one, and listen for it in the parent with #[On('post-saved')]. This avoids the classic bug where a modal closes but the table behind it is stale.

Stacked flows. Because the package supports stacks, model multi-step wizards as a sequence of small components: step one calls Modal::open(StepTwo::class, $payload) and Modal::close(). Each step owns its own validation; the stack handles the back-navigation. Prefer separate classes over one giant component with a $step property — state resetting on close becomes free.

Pass data in, not out. Pass scalars/IDs into Modal::open(), not Eloquent models, and re-fetch inside mount(). It keeps the component testable and avoids stale-model surprises if the record changes while the modal is open.

Reusable chrome. Publish the views and point your own components at a shared modal Blade layout (<x-modal-panel>) so header/footer/close-button styling lives in one place. Do this on day one; retrofitting twenty modals later is tedious.

Testing. Test modal components exactly like any Livewire component (Livewire::test(Edit::class, ['postId' => $post->id])). For the trigger side, assert on the facade/event rather than rendering the host, and only mount the host component in the one or two integration tests that verify the open/close wiring end to end.

Gotchas and Tips

  • Mount the host exactly once. Two <livewire:modal /> instances produce duplicated overlays and confusing close behaviour. Put it in the base layout, never in a page view.
  • wire:navigate and the host. If you use Livewire's SPA navigation, ensure the host sits outside the navigated content, otherwise the container gets destroyed mid-transition and modals appear to "stick" open.
  • Close-then-redirect. Don't Modal::close() and redirect() in the same request and expect a clean unmount — the dispatched close can be lost as the page swaps. Either redirect and let the page reload, or close and emit a parent event.
  • Fresh mount vs. stale state. Reopening a modal usually remounts the component, but if you keep a single instance alive, remember to reset() and resetValidation(). The safest habit: treat every modal as ephemeral and never rely on state surviving a close.
  • Validation errors live inside the modal. If the save logic sits in the parent instead of the modal component, $errors won't render where the form is. Keep validation next to the form.
  • wire:key on repeated components. Any @foreach that renders modal-triggering items needs a stable wire:key, or Livewire will reuse the wrong component and you'll open the previous record.
  • Backdrop/Escape handling. If you customize the published views, keep the package's JS hooks intact — removing the wrapper class or teleport target is the usual cause of "modal renders behind the navbar".
  • Body scroll lock. The package handles it, but if you also manipulate overflow-hidden on <html> in your own JS, they'll fight and the page will stay locked after close. Pick one owner.
  • Debugging. When a modal won't open, check in this order: host present on the page → config not cached (php artisan config:clear) → the component class path passed to Modal::open() is fully qualified → a listener isn't swallowing the event. Ninety percent of reports are one of those four.
  • Extension point worth knowing: the config's stack/limit settings and the publishable views are the intended customization seams. Forking the package for styling is almost never necessary — publish, override, keep the JS.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource