Corepine Modal is a stack-based modal system for Laravel with two runtime modes:
standalone Alpine + Blade modals
Livewire stack-based modals
modal (dialog)
drawer (left or right panel)
sheet (bottom sheet)
It supports:
extends Corepine\Modal\Modal)^8.2|^8.3|^8.4^11.0|^12.0|^13.0^3.7|^4.0Livewire is required by the package because stack mode uses a Livewire host. Standalone Alpine + Blade usage is still fully supported.
composer require corepine/modal
Publish config:
php artisan vendor:publish --tag=corepine-modal-config
Render the host once in your layout:
<x-corepine.modal.assets />
You can use <x-corepine.modal /> directly with browser events and no Livewire modal class.
The host is not required for standalone-only usage.
Add the package stylesheet to your main CSS entry:
@import "../../vendor/corepine/modal/resources/css/app.css";
The package CSS already includes Tailwind @source paths for its own views and PHP classes.
<?php
namespace App\Livewire\Modals;
use App\Models\User;
use Corepine\Modal\Actions\Action;
use Corepine\Modal\Enums\ModalType;
use Corepine\Modal\Modal;
use Corepine\Support\Enums\Placement;
class EditUser extends Modal
{
public User $user;
public static function modalAttributes(): array
{
return [
'type' => ModalType::Modal,
'placement' => Placement::Center,
'origin' => Placement::Center,
'shell' => true,
'heading' => 'Edit User',
'description' => 'Update account details',
'showClose' => true,
'dismissible' => true,
'closeOnEscape' => true,
'actions' => [
Action::make('cancel')->label('Cancel')->close(),
Action::make('save')->label('Save')->primary()->action('save'),
],
];
}
}
Open it from Blade:
<button
type="button"
onclick="Livewire.dispatch('modal.open', { component: 'modals.edit-user', arguments: { user: {{ $user->id }} } })"
>
Edit User
</button>
From a modal class:
$this->openModal('modals.edit-user', ['user' => 5]);
$this->openBottomSheet('modals.user-sheet', ['user' => 5]);
$this->closeModal();
$this->closeModal(
destroy: false,
dispatch: ['users-refreshed' => ['user' => 5]],
dispatchTo: ['orders.table' => ['sync-user' => ['user' => 5]]],
);
$this->closeTopModal(
layers: 2,
dispatch: ['users-refreshed' => ['user' => 5]],
);
$this->closeAll(
dispatchTo: ['orders.table' => ['sync-user' => ['user' => 5]]],
);
From Blade helpers:
<x-corepine.modal.actions.open component="modals.edit-user" :arguments="['user' => $user->id]">
<button type="button">Edit</button>
</x-corepine.modal.actions.open>
<x-corepine.modal.actions.open modal-id="user-sheet">
<button type="button">Open Sheet</button>
</x-corepine.modal.actions.open>
<x-corepine.modal.actions.close
layers="1"
:destroy="true"
:dispatch="['users-refreshed' => ['user' => $user->id]]"
:dispatch-to="['orders.table' => ['sync-user' => ['user' => $user->id]]]"
>
Close
</x-corepine.modal.actions.close>
dispatch fires regular Livewire/browser events after the close finishes.
dispatchTo fires Livewire targeted events after the close finishes.
Use modal-id on open/close helpers when you want to target a standalone <x-corepine.modal id="..." /> by id.
Use this when you do not need a Livewire modal class:
<button
type="button"
onclick="window.dispatchEvent(new CustomEvent('modal.open', { detail: { id: 'user-sheet' } }))"
>
Open Sheet
</button>
<x-corepine.modal id="user-sheet" type="sheet" heading="User Details">
<p class="text-sm text-zinc-600">Standalone modal body</p>
<x-slot:footer>
<button
type="button"
class="rounded-md border px-3 py-2 text-sm"
onclick="window.dispatchEvent(new CustomEvent('modal.close', { detail: { id: 'user-sheet' } }))"
>
Close
</button>
</x-slot:footer>
</x-corepine.modal>
Standalone named slots:
header: custom header content. Slot attributes are merged onto the header wrapper classes.header slot is provided (even empty), it overrides built-in heading/description/close rendering.footer: custom footer content.Example:
<x-corepine.modal id="custom-header-modal" show-close="false">
<x-slot:header class="font-bold text-lg" data-testid="custom-header">
Custom Header
</x-slot:header>
<p>Body</p>
</x-corepine.modal>
Standalone browser events:
modal.openmodal.closemodal.toggleEach event accepts { id?: string } in detail.
The canonical shell/action API uses actions (not legacy keys).
| Key | Type | Default | Notes |
|---|---|---|---|
type |
modal | drawer | sheet |
modal |
Presentation type. |
placement |
Placement | string |
by type | modal: center/top/bottom/left/right, drawer: left/right, sheet: forced bottom. |
origin |
Placement | string |
follows type/placement | Transform origin; same value set as placement vocabulary. |
size |
string |
default |
Width token from config sizes, or custom class string. |
height |
string | number | null |
null |
Panel height (modal/drawer) and initial sheet height. |
maxHeight |
string | number | null |
null |
Shared max-height cap for all types. |
dismissible |
bool |
true |
Scrim click closes when true. |
draggable |
bool |
type-aware | Sheet drag/resize behavior. |
showDragHandle |
bool |
type-aware | Sheet handle visibility. |
dragCloseThreshold |
float |
0.3 |
Sheet drag-close ratio. |
closeOnEscape |
bool |
true |
Escape closes top layer. |
closeAllOnEscape |
bool |
false |
Escape closes full stack. |
destroyOnClose |
bool |
true |
Remove closed layers from host state. |
dispatch |
array |
[] |
Default events to dispatch after close. |
dispatchTo |
array |
[] |
Default targeted Livewire events to dispatch after close. |
dispatchCloseEvent |
bool |
false |
Emits the built-in modal.component-closed notification for that layer. |
blur |
bool |
false |
Scrim blur effect. |
shell |
bool |
true |
Enables built-in shell header/body/footer structure. |
heading |
string | null |
null |
Shell heading text. |
description |
string | null |
null |
Shell description text. |
showClose |
bool | null |
auto |
Built-in shell close icon. Defaults to visible only when built-in heading or description is present. |
footerActionsAlignment |
Alignment | string |
end |
start, center, end. |
actions |
array |
[] |
Declarative shell actions (close / method). |
class |
string |
'' |
Extra panel classes. |
sheet: always renders from bottom and always uses placement=bottom, origin=bottom.drawer: only left and right are valid.modal: supports all five placement values and now fully respects both placement and origin.Action payloads can be raw arrays or fluent Action::make(...) objects.
use Corepine\Modal\Actions\Action;
'actions' => [
Action::make('cancel')
->label('Cancel')
->close(),
Action::make('save')
->label('Save')
->primary()
->action('save'),
]
Supported fluent helpers include:
method() / action()close(layers, destroy, closeAll)dispatch() / dispatchTo() on close actionsdisabled()visible()color() and shortcuts (primary, danger, success, warning, info, gray, dark)accent()outline()attributes()Default incoming events:
modal.openmodal.open-sheetmodal.closemodal.close-topmodal.close-allmodal.destroymodal.resetmodal.toggleDefault outgoing events:
modal.openedmodal.closedmodal.changedmodal.all-closedmodal.component-closedYou can rename both incoming and outgoing events in config/corepine-modal.php:
'events' => [
'listen' => [
'open' => 'acme.modal.open',
'close' => 'acme.modal.close',
],
'dispatch' => [
'opened' => 'acme.modal.opened',
],
],
For package integrations, do not hardcode event strings. Resolve them from the service:
use Corepine\Modal\Facades\Modal;
$openEvent = Modal::event()->openModal();
$closeEvent = Modal::event()->closeModal();
Main sections in config/corepine-modal.php:
events.listen: incoming event namesevents.dispatch: outgoing event namesdefaults.attributes: global modal attribute defaultssizes: modal width tokens'sizes' => [
'default' => 'max-w-xl sm:max-w-full',
'editor' => 'max-w-[960px]',
],
<x-corepine.modal.assets /><x-corepine.modal /><x-corepine.modal.layout /><x-corepine.modal.footer /><x-corepine.modal.actions.open /><x-corepine.modal.actions.close />How can I help you explore Laravel packages today?