Skip to content

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 $data array property that persists across step changes — wire inputs with wire: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.