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

Modal Laravel Package

mrshanebarron/modal

A Laravel package for creating modal dialogs in your applications. Render configurable, reusable modals from Blade views with a simple API for opening, closing, and passing data—keeping markup and behavior organized without extra JavaScript wiring.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin

composer require mrshanebarron/modal:^1.0.1
php artisan vendor:publish --provider="MrShaneBarron\Modal\ModalServiceProvider"

Pin the exact minor (^1.0.1), not *. v1.0.1 is the first release that renamed the config file to the sb- prefix — you want to be on it deliberately, not drift onto it.

Where to look first, in this order

  1. vendor/mrshanebarron/modal/src/ — the modal class base and the facade/helper. This is the real API surface; the README on a 0-star v1.0.x package is not.
  2. The service provider's register()/boot() — confirms the view namespace and the registered Blade component alias (<x-sb-modal> or similar).
  3. config/sb-modal.php (name per the v1.0.1 fix) — confirm the actual key before you grep for it. The config namespace is sb-*, so consumer code reads config('sb-modal.…').
  4. The published Blade view — this is where the mount point, backdrop and focus-trap live. Read it once; it explains every rendering bug you will have.

First use case: the destructive-action confirm

Almost every app needs "are you sure?" before a delete. Wire that first, end-to-end, because it exercises the whole loop — open from a controller, render in the layout, submit, close.

// app/Http/Controllers/PostController.php
public function destroy(Post $post)
{
    $post->delete();

    return back()->with('status', 'Post deleted.');
}

public function confirmDestroy(Post $post)
{
    // Shape of the API per the package description — verify exact
    // method names against src/ before writing against them.
    return Modal::open('confirm-delete')
        ->size('sm')
        ->data(['post' => $post]);
}

The single most important setup step: put the mount point in your layout, once, after the main content and before </body>.

{{-- resources/views/layouts/app.blade.php --}}
        @yield('content')
        <x-sb-modal />   {{-- confirm the actual tag name --}}
    </body>
</html>

If you skip this, every Modal::open() succeeds, the redirect succeeds, and nothing appears. That is the #1 first-day failure.

Implementation Patterns

Two kinds of modal, two places they live

Use it for Define as
Simple confirm, alert, small info panel A named entry in config, or a short Blade view
Form with validation, selects, file upload Its own Blade view file, always

Keep the "class" modals to a few lines. As soon as a modal has more than one input, promote it to a dedicated Blade file (resources/views/modals/edit-post.blade.php) so it is diffable and reviewable like any other view. Never build a modal inline in a controller with a big heredoc — you lose Blade escaping and IDE support.

Open from a controller (request → render)

return Modal::open('edit-post')->size('lg')->data(compact('post'));

Open from a redirect (the important one)

This is where the package earns its keep: session-flash the open instruction, then back(). Use it for validation failures, which is the pattern people get wrong most often.

public function update(UpdatePostRequest $request, Post $post)
{
    $post->update($request->validated());

    return back()->with('status', 'Saved.');
}

When UpdatePostRequest fails, Laravel flashes $errors and old input, then redirects back. Your controller never runs — so the modal must reopen itself on the errors. The reliable way is to render the modal inside the form's own view and gate it on $errors->any():

@if ($errors->any())
    <x-sb-modal name="edit-post" :open="true" size="lg">
        @include('modals._edit-post-form', ['post' => $post])
    </x-sb-modal>
@else
    <x-sb-modal name="edit-post" size="lg">
        @include('modals._edit-post-form', ['post' => $post])
    </x-sb-modal>
@endif

Put the actual <form> and its fields in the partial so you have one source of truth. Do not duplicate the form markup between the "closed" and "reopened" states — that is how the two copies drift and you get a modal that loses old() on the second failed submit.

Livewire components

Keep the modal's open/close state in a public property (public bool $showEditModal = false) and let the package render inside the Livewire component's view. The failure mode to avoid: rendering the modal outside the Livewire root and toggling it with $dispatch. That works until Livewire re-renders and morphs the DOM, which breaks the focus trap. If you must dispatch across the boundary, wrap the modal container in wire:ignore — but prefer colocating.

Stacked modals

Stacking is the feature to demo, and the one to use sparingly. The legitimate case is a confirm on top of a form: the edit modal opens a "discard changes?" or "delete nested record?" dialog. The ordering rule is simple — open the child after the parent in the same request, and the package paints the second backdrop above the first and holds scroll-lock until both close. Do not stack three deep; the focus trap becomes genuinely confusing and users lose track of what "Cancel" refers to.

Sizing

Drive it from config defaults, override per-call. That keeps the call sites short and lets you restyle the whole app in one file:

// config/sb-modal.php (published)
'default_size' => 'md',
'sizes' => ['sm' => 'max-w-sm', 'md' => 'max-w-lg', 'lg' => 'max-w-2xl'],

Testing

Assert on the flash payload rather than on HTML where you can — it is stable across view changes:

$this->get(route('posts.confirm-destroy', $post))
    ->assertSessionHas('modal'); // confirm actual key in src/

$this->post(route('posts.update', $post), ['title' => ''])
    ->assertSessionHasErrors('title')
    ->assertRedirect();

Then exactly one feature test that hits a page and assertSees the modal markup, to prove the mount point is in the layout. One is enough; it catches the whole class of "nobody sees any modal" regressions.

Gotchas and Tips

The v1.0.1 config rename is the one thing to check on day one. The release note is literally "Fix config file naming (sb- prefix)." If this package was present in the app at v1.0.0, look in config/ for a stale file under the old name. Two config files means ambiguous precedence and silent fallback to package defaults — the app boots fine, the modal just behaves differently than the file you carefully edited. Clean up, then:

php artisan config:clear
# and in the deploy pipeline:
php artisan config:cache

Stale bootstrap/cache/config.php is the most common reason "the fix didn't apply."

Nothing renders → the mount point is missing. Before you debug the package, grep your layout for the modal component tag and confirm it sits before </body> and outside any element with overflow: hidden or transform. This accounts for the majority of "it's not working" reports on a package like this.

Duplicate IDs when rendering in a loop. If you render a modal per row (e.g. per-item delete confirmation), each needs a unique name/id. Duplicate IDs break the focus trap, and you will get the first modal's content regardless of which button you clicked. Prefer one modal + ->data(['id' => $post->id]) over N modals.

Validation errors and old(). Two common slips: (a) using redirect()->route(...) instead of back() in the failed-validation path, which lands the user on a page without the modal; (b) forgetting value="{{ old('title', $post->title) }}" in the partial, so a failed submit wipes the user's typing. Test the failed submit explicitly — it is the path no one clicks during manual QA.

Scroll-lock leaks. If a modal is removed from the DOM by a Livewire morph or an href navigation before the package's close handler runs, body { overflow: hidden } can stick. Symptom: page looks fine but won't scroll. Guard by ensuring any custom close button calls the package's close path rather than hiding the element directly, and treat "page won't scroll" as a known symptom in your team runbook.

Config caching is safe only if the package is honest about it. After publishing, check config/sb-modal.php for runtime env() calls outside it. Standard Laravel footgun, and it bites harder on tiny packages where nobody reviewed the provider. If you find env() in src/, file it upstream and read config through config() in your own code.

Extension points, in order of preference. Publish and edit the view (safest — it's your file now) → override the Blade component from your own namespace → subclass the modal class. Avoid patching vendor/; with a single-maintainer v1.0.x package, your patch will be blown away by the next composer update and no one will notice until production.

Keep the modal class cheap. Don't query the database in a modal definition constructor. Build the data in the controller and pass it via ->data(). Otherwise every request that might open a modal pays for the query, and the N+1 shows up in a page that nominally does nothing.

Debugging recipe. Dump the session after a request (session()->all() via a throwaway route or dd() in the controller) and confirm the open-modal payload is actually there. Ninety percent of issues split cleanly: payload absent = a PHP/controller problem; payload present but nothing on screen = a view/mount-point problem. Knowing which half you're in saves the most time on this package.

Pin and watch. At v1.0.1 with one maintainer, treat minor bumps as reviewable changes: check CHANGELOG/releases for config-key or view-path renames, re-publish and diff the views, then run your one feature test that asserts modal markup renders. Budget a version bump per Laravel major — that's where small UI packages go stale.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package