ウィザードフォーム——複数ステップのWizardコンポーネント詳解
Wizardが必要な場面
一度に多くの情報を入力するフォームはユーザーに負担をかけます。Wizardコンポーネントを使うと、フォームを複数のステップに分割し、段階的に入力を進めるUXが実現できます。
典型的なユースケース:
- 会員登録フロー(基本情報→プロフィール→支払い設定)
- 商品登録(基本情報→バリエーション→価格設定→在庫)
- イベント申込(参加者情報→オプション選択→確認)
基本的なWizardの実装
use Filament\Forms\Components\Wizard;
use Filament\Forms\Components\Wizard\Step;
public static function form(Form $form): Form
{
return $form
->schema([
Wizard::make([
Step::make('基本情報')
->description('商品の基本的な情報を入力してください')
->icon('heroicon-o-pencil-square')
->schema([
TextInput::make('name')
->label('商品名')
->required()
->maxLength(255),
Select::make('category_id')
->label('カテゴリ')
->relationship('category', 'name')
->required(),
Textarea::make('description')
->label('商品説明')
->rows(4),
]),
Step::make('価格と在庫')
->description('価格と在庫数を設定してください')
->icon('heroicon-o-currency-yen')
->schema([
TextInput::make('price')
->label('販売価格')
->numeric()
->prefix('¥')
->required()
->minValue(0),
TextInput::make('compare_at_price')
->label('比較価格(定価)')
->numeric()
->prefix('¥'),
TextInput::make('stock_quantity')
->label('在庫数')
->numeric()
->default(0)
->minValue(0),
Toggle::make('track_inventory')
->label('在庫管理を有効化')
->default(true),
]),
Step::make('画像とSEO')
->description('画像のアップロードとSEO設定')
->icon('heroicon-o-photo')
->schema([
FileUpload::make('images')
->label('商品画像')
->image()
->multiple()
->maxFiles(8)
->columnSpanFull(),
TextInput::make('meta_title')
->label('SEOタイトル')
->maxLength(60),
Textarea::make('meta_description')
->label('SEOディスクリプション')
->maxLength(160)
->rows(2),
]),
])
->columnSpanFull()
->skippable(false), // ステップを順番通りに進める
]);
}
ステップごとのバリデーション
各ステップで「次へ」ボタンを押すと、そのステップのフィールドのみバリデーションが実行されます。次のステップに進むためにはすべてのバリデーションを通過する必要があります。
Step::make('会員情報')
->schema([
TextInput::make('email')
->label('メールアドレス')
->email()
->required()
->unique('users', 'email') // メール重複チェックも可能
->maxLength(255),
TextInput::make('password')
->label('パスワード')
->password()
->required()
->minLength(8)
->same('password_confirmation'),
TextInput::make('password_confirmation')
->label('パスワード(確認)')
->password()
->required()
->dehydrated(false), // DBに保存しない
]),
->afterValidation() でステップ完了時の処理
ステップのバリデーション後に追加処理を実行できます。
Step::make('メールアドレス確認')
->schema([
TextInput::make('email')
->label('メールアドレス')
->email()
->required(),
TextInput::make('verification_code')
->label('確認コード')
->required()
->length(6)
->numeric(),
])
->afterValidation(function (array $state): void {
// 確認コードをチェック
$storedCode = cache()->get("verification_code_{$state['email']}");
if ($storedCode !== $state['verification_code']) {
throw ValidationException::withMessages([
'verification_code' => '確認コードが正しくありません',
]);
}
}),
Wizard付きCreateページ
ResourceのCreateページをWizardに置き換えることができます。
// app/Filament/Resources/ProductResource/Pages/CreateProduct.php
namespace App\Filament\Resources\ProductResource\Pages;
use App\Filament\Resources\ProductResource;
use Filament\Resources\Pages\CreateRecord;
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
class CreateProduct extends CreateRecord
{
use HasWizard;
protected static string $resource = ProductResource::class;
protected function getSteps(): array
{
return [
Step::make('基本情報')
->schema([
TextInput::make('name')->required(),
Select::make('category_id')
->relationship('category', 'name')
->required(),
]),
Step::make('価格と在庫')
->schema([
TextInput::make('price')->numeric()->required(),
TextInput::make('stock_quantity')->numeric()->default(0),
]),
Step::make('確認')
->schema([
Placeholder::make('summary')
->content(fn (Get $get) =>
"商品名: {$get('name')}\n価格: ¥" . number_format($get('price'))
),
]),
];
}
}
条件付きステップのスキップ
->skippable()をtrueにすると、ユーザーが任意のステップをスキップできるようになります。
Wizard::make([
Step::make('基本情報')->schema([...]),
Step::make('オプション情報')->schema([...]),
Step::make('確認')->schema([...]),
])
->skippable()
コードでステップをスキップ(リンクとして定義)する場合はStep::make()->skippable():
Step::make('オプション情報')
->schema([...])
// このステップはスキップ可能
Wizardの完了後処理をカスタマイズ
class CreateOrder extends CreateRecord
{
use HasWizard;
protected function afterCreate(): void
{
// ウィザード完了後に実行される処理
$order = $this->getRecord();
// 注文確認メール送信
Mail::to($order->customer->email)
->send(new OrderConfirmationMail($order));
// 在庫の減算
foreach ($order->items as $item) {
$item->product->decrement('stock_quantity', $item->quantity);
}
Notification::make()
->title('注文を受け付けました')
->body("注文番号: #{$order->order_number}")
->success()
->send();
}
protected function getCreatedNotificationTitle(): ?string
{
return null; // デフォルト通知を無効化(afterCreate内で送信するため)
}
}
コツ・注意点・ハマりポイント
コツ: ステップ数は3〜5が最適です。それ以上になる場合はTabsコンポーネントの使用や、フォームの設計を見直すことを検討してください。最後のステップは「確認ステップ」として入力内容の確認画面にすると、ユーザーが送信前に内容を確認できて誤操作を防げます。
注意点: WizardはLivewireのステート管理に依存しているため、ページをリロードするとステップ1に戻ります。重要なフォームデータはセッションや一時保存機能(draft状態のレコード)でバックアップすることを検討してください。
ハマりポイント: Wizard内のフィールドに->relationship()を使う場合、リレーション保存のタイミングに注意が必要です。Wizardの各ステップでは実際のDB保存は行われず、すべてのステップ完了時にまとめて保存されます。
まとめ
FilamentのWizardコンポーネントは複数ステップのフォームを簡単に実装できます。HasWizardトレイトを使えばResourceのCreateページをウィザード化でき、ステップごとのバリデーション・afterValidation()フック・完了後処理を組み合わせることで、複雑な入力フローを整理されたUXで提供できます。