Overview
AuraWizardPage turns a Filament page into a multi-step wizard. You get:
- A primary-themed step indicator across the top (numbered circles with connector lines and a glowing progress bar).
- URL-synced step state (
?step=2) so a half-completed wizard is bookmarkable. - A shared
$dataarray property that persists across step changes — wire inputs withwire:model="data.field_name". - Per-step validation via a
->validate()closure that runs before advancing. - Optional steps that render a "Skip" button.
- Back / Next / Finish nav with smart labels (Next flips to Finish on the last step).
- An
onInvalidStep()hook to surface a Filament Notification or whatever UX you prefer.
This is the page-level wizard (a full screen split into stages). For inline multi-step forms inside a resource, use Filament's built-in Filament\Schemas\Components\Wizard.
Minimal example
// app/Filament/Pages/SetupWizard.php
namespace App\Filament\Pages;
use App\Models\Project;
use BlueStarSystem\AuraFilament\Pages\AuraWizardPage;
use BlueStarSystem\AuraFilament\Support\WizardStep;
class SetupWizard extends AuraWizardPage
{
protected string $view = 'filament.pages.setup-wizard';
protected static ?string $navigationLabel = 'Setup';
protected function getSteps(): array
{
return [
WizardStep::make('basics')
->label('Basics')
->icon('heroicon-o-identification')
->validate(fn (array $data) => ! empty(trim($data['project_name'] ?? ''))),
WizardStep::make('brand')
->label('Brand')
->icon('heroicon-o-swatch')
->validate(fn (array $data) => preg_match('/^#?[0-9a-f]{6}$/i', $data['color'] ?? '') === 1),
WizardStep::make('team')
->label('Team')
->icon('heroicon-o-users')
->optional(),
WizardStep::make('review')
->label('Review')
->icon('heroicon-o-check-badge'),
];
}
protected function complete(): void
{
Project::create($this->data);
$this->redirect('/admin/projects');
}
}
{{-- resources/views/filament/pages/setup-wizard.blade.php --}}
<x-filament-panels::page>
<x-aura-filament::wizard :page="$this">
@switch($this->currentStep)
@case(0)
<label class="block text-sm font-medium">
Project name
<input type="text" wire:model.live="data.project_name"
class="mt-2 block w-full rounded-lg border px-3 py-2">
</label>
@break
@case(1)
<label class="block text-sm font-medium">
Primary color
<input type="text" wire:model.live.debounce.400ms="data.color"
class="mt-2 block w-full rounded-lg border px-3 py-2 font-mono"
placeholder="#7c3aed">
</label>
@break
@case(2)
{{-- optional team step --}}
<textarea wire:model="data.invites" rows="4"
class="mt-2 block w-full rounded-lg border px-3 py-2 font-mono"></textarea>
@break
@case(3)
<dl class="divide-y">
<div class="flex justify-between p-3">
<dt>Project</dt>
<dd>{{ $this->data['project_name'] }}</dd>
</div>
<div class="flex justify-between p-3">
<dt>Color</dt>
<dd class="font-mono">{{ $this->data['color'] }}</dd>
</div>
</dl>
@break
@endswitch
</x-aura-filament::wizard>
</x-filament-panels::page>
WizardStep API
WizardStep::make(string $key)
->label(string $label)
->description(?string $description)
->icon(?string $icon) // heroicon / phosphor name
->optional(bool $optional = true) // render a Skip button
->validate(Closure $callback); // runs before advancing
->validate() receives the current $data array. Return truthy to allow the advance. A broken closure (thrown exception) is treated as invalid — the user stays on the current step, never crashes.
Page API
The abstract AuraWizardPage exposes:
| Method | Purpose |
|---|---|
getSteps(): array |
Abstract — return the ordered step list |
complete(): void |
Abstract — called on Finish (persist $data, redirect) |
nextStep() |
Validate + advance (or call complete() on the last step) |
previousStep() |
Go back (floors at 0) |
goToStep(int $i) |
Jump to any step, clamped to valid range. Called when the user clicks a marker in the indicator |
skipStep() |
Advance only if the current step is ->optional() |
finishWizard() |
Programmatic Finish (validates the last step first) |
onInvalidStep(WizardStep $step) |
Hook — default no-op. Override to dispatch a Notification |
isFirstStep() : bool |
For your own UI |
isLastStep() : bool |
For your own UI |
getCurrentStep() : ?WizardStep |
The active step config |
getStepCount() : int |
How many steps total |
public int $currentStep |
Livewire property, synced to ?step= query string |
public array $data |
Livewire state shared across steps — wire:model="data.field" |
Surfacing validation errors
By default a blocked advance does nothing visible. The common pattern is to override onInvalidStep() with a Filament Notification:
use Filament\Notifications\Notification;
protected function onInvalidStep(\BlueStarSystem\AuraFilament\Support\WizardStep $step): void
{
Notification::make()
->title('Please complete the required fields')
->body("The {$step->getLabel()} step has a required field that needs attention.")
->warning()
->send();
}
For fine-grained per-field errors, run Laravel's validator inside your closure and push messages into the session before returning false:
WizardStep::make('basics')
->validate(function (array $data) {
$validator = validator($data, [
'project_name' => ['required', 'string', 'min:3'],
]);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $message) {
\Filament\Notifications\Notification::make()->title($message)->danger()->send();
}
return false;
}
return true;
});
Live showcase
A 4-step setup wizard (Basics → Brand → Team → Review) is running on demo.aura-ui.com/admin/setup-wizard. Bookmarkable step state, primary-themed indicator, warning notification on failed validation.