K

Core

Modals

Modal system for confirmation dialogs, slide-overs, and multi-step wizards.

Modals preview
On this page

Modal system for confirmation dialogs, slide-overs, and multi-step wizards.

Class Description
Modal Standard centered modal
ConfirmationDialog Modal with confirm/cancel buttons
SlideOver Panel sliding from the right
Wizard Multi-step wizard with step navigation

Confirmation Dialog

Most common use — triggered from Actions:

Action::make('delete')
->requiresConfirmation()
->modalHeading('Delete this record?')
->modalDescription('This action cannot be undone.')
->modalIcon('trash', 'danger')
->modalSubmitActionLabel('Yes, delete')
->modalCancelActionLabel('Cancel')
->action(fn ($record) => $record->delete());

Slide-Over

Panel slides in from the right:

Action::make('details')
->slideOver()
->stickyHeader()
->stickyFooter()
->modalMaxHeight('60vh');
Action::make('edit')
// Size
->modalWidth('2xl') // sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, full
 
// Close behavior
->closeModalOnClickAway()
->closeModalOnEscape()
 
// Mobile adaptations
->slideOverOnMobile() // bottom-sheet on mobile, dialog on desktop
->fullScreenOnMobile() // full screen on mobile
->mobileBreakpoint('md'); // where the sheet kicks in (sm|md|lg)

Below the mobile breakpoint, slideOverOnMobile() renders the form modal as a bottom-sheet that slides up from the bottom edge, and fullScreenOnMobile() fills the viewport; both scroll the body inside the panel so the footer buttons stay visible, and both keep the centered dialog unchanged on desktop. Sheets add safe-area padding, a drag-to-dismiss grabber and a focus trap automatically.

The breakpoint defaults to the global wire-core.mobile.breakpoint (sm, i.e. < 640px) and can be raised per action with ->mobileBreakpoint('md') (< 768px, includes small tablets) or 'lg' (< 1024px). See Configuration → Mobile for the global default.

Instead of the fluent ->modal*() setters, an action can be configured from a declarative modal object. Pass any ModalContractModal, SlideOver, ConfirmationDialog, or Wizard — to ->modal():

use NyonCode\WireCore\Modals\ConfirmationDialog;
use NyonCode\WireCore\Modals\Modal;
use NyonCode\WireCore\Modals\SlideOver;
use NyonCode\WireCore\Modals\Wizard;
 
// Centered dialog
Action::make('edit')->modal(
Modal::make()
->heading('Edit record')
->description('Update the details below')
->width('lg')
->icon('pencil', 'primary')
);
 
// Slide-over panel (->mobileOnly() = slide-over on mobile, modal on desktop)
Action::make('view')->modal(
SlideOver::make()->heading('Details')->width('xl')
);
 
// Confirmation dialog — with presets (delete / makeDanger / makeWarning / makeInfo)
Action::make('delete')->modal(
ConfirmationDialog::delete('User')
);
 
// Multi-step wizard (see below)
Action::make('create')->modal(
Wizard::make()->heading('Create user')->steps([/* ModalStep::make(...) */
);

The config object's values are translated into the action's modal state and rendered through the same runtime as the fluent setters — there is a single canonical modal owner, so both styles behave identically.

Custom buttons in the modal footer:

use NyonCode\WireCore\Actions\ModalFooterAction;
 
Action::make('edit')
->form([...])
->modalFooterActions([
ModalFooterAction::make('save')
->label('Save')
->color('primary')
->submitsForm(),
 
ModalFooterAction::make('save-and-close')
->label('Save & Close')
->action(fn () => $this->saveAndClose()),
 
ModalFooterAction::make('cancel')
->label('Cancel')
->color('gray')
->outlined()
->closesModal(), // closes the modal
 
ModalFooterAction::make('reset')
->requiresConfirmation() // asks before running (native wire:confirm)
->confirm('Really reset the form?') // …or with a custom message
->action(fn ($set) => $set('name', '')),
]);

Multi-Step Wizard

Give an action multiple steps with ->steps([...]) (or a Wizard object via ->modal()). The modal renders a step indicator with Back / Next / Submit navigation; each step validates before advancing, data is shared across all steps, and the final submit re-validates every step.

use NyonCode\WireCore\Actions\ModalStep;
 
Action::make('create')
->steps([
ModalStep::make('Basic Info')
->description('Enter user details')
->icon('user')
->schema([
TextInput::make('name')->required(),
TextInput::make('email')->email()->required(),
])
->validation(['name' => 'required|min:2']), // extra per-step rules
 
ModalStep::make('Settings')
->schema([
Select::make('role')->options([...]),
Toggle::make('active'),
])
->afterValidation(fn (array $data) => logger('step 2 passed', $data)),
 
ModalStep::make('Review')
->before(fn (array $data) => ['summary' => "Creating {$data['name']}"]) // pre-fill
->schema([
Placeholder::make('summary')
->content(fn ($data) => $data['summary'] ?? ''),
]),
])
->action(fn ($record, $data) => User::create($data));

Each step writes to the same form-data bag, so values entered earlier persist as the user moves back and forth. Validation on Next runs the step's field rules (via the form runtime), then any ->validation() rules, then the afterValidation hook; the next step's before hook can return an array to pre-fill it. Submit re-validates every step cumulatively (the afterValidation hooks are not re-run, so they never fire twice).

Customising the navigation labels

The wizard's Back, Next and the submit-in-progress Saving… labels are configurable and fall back to translatable defaults (wire-core::actions.{wizard_previous,wizard_next,submit_saving}):

Action::make('create')
->steps([/* ... */])
->modalPreviousActionLabel('Back')
->modalNextActionLabel('Continue')
->modalSubmitActionLabel('Create user')
->modalSavingLabel('Creating…');

modalFooterActions() render in a wizard footer too, alongside Back / Next / Submit.

Building a step from earlier data

Pass a Closure to ->schema() to build a step's fields from the values entered in previous steps. The Closure receives the accumulated form-data bag, so a later step can adapt to an earlier choice. This works for row, bulk and header actions — a header action carries no record, so its step Closures still receive the live form-data bag (not null):

HeaderAction::make('create')
->steps([
ModalStep::make('Type')
->schema([
Select::make('kind')
->options(['business' => 'Business', 'person' => 'Person']),
]),
 
ModalStep::make('Details')
// $data holds everything entered so far (here: 'kind' from step 1).
->schema(fn (array $data) => [
TextInput::make('name')->required(),
...($data['kind'] === 'business'
? [TextInput::make('vat_id')->required()]
: [TextInput::make('birth_date')]),
]),
])
->action(fn (array $data) => Customer::create($data));

ModalStep API

ModalStep::make(string $label)
->description(?string $description)
->icon(string|Icon|null $icon)
->schema(array|Closure $fields)
->validation(array|Closure $rules) // extra rules, keyed by field name
->validationMessages(?array $messages)
->afterValidation(Closure $callback) // runs after the step validates
->before(Closure $callback) // runs before the step shows; return array to pre-fill

Halt Modal

ActionHalt creates a secondary confirmation modal mid-execution:

Action::make('process')
->before(function ($record, Action $action) {
if ($record->has_warnings) {
$action->halt()
->heading('Warnings Detected')
->body('There are unresolved warnings. Continue anyway?')
->icon('exclamation', 'warning')
->submitLabel('Continue')
->cancelLabel('Cancel')
->width('md');
}
})
->action(fn ($record) => $record->process());

ActionHalt API

->heading(string $heading)
->body(string $body)
->icon(string $icon, ?string $color)
->submitLabel(string $label)
->cancelLabel(string $label)
->width(string $width)
->validation(array $rules) // validate form data before continue

Blade Components

<x-wire-modals::modal />
<x-wire-modals::confirmation />