カスタムフォームコンポーネント——独自フィールドを作る
なぜカスタムコンポーネントが必要か
Filamentは豊富な標準フォームフィールド(TextInput・Select・DatePicker など)を提供しています。しかし実際の開発では、カラーピッカー・住所入力・独自のUIコントロールが必要になることがあります。そういった場合にカスタムフォームコンポーネントを自作します。
カスタムコンポーネントを作ることで、Filamentの標準フィールドと同じAPIで使えるようになり、チーム内での再利用性も高まります。
php artisan make:filament-form-field でスキャフォールド生成
php artisan make:filament-form-field ColorPicker
このコマンドで以下のファイルが生成されます。
app/Forms/Components/ColorPicker.php
resources/views/forms/components/color-picker.blade.php
生成されたPHPクラスの初期状態:
namespace App\Forms\Components;
use Filament\Forms\Components\Field;
class ColorPicker extends Field
{
protected string $view = 'forms.components.color-picker';
}
Fieldを継承しているため、標準フィールドと同じように->label()・->required()・->disabled()が使えます。
view() メソッドとBladeテンプレート
$viewプロパティで対応するBladeテンプレートを指定します。Bladeテンプレート側では$getState()・$setState()などのクロージャが自動的に利用可能です。
@php
$state = $getState();
@endphp
<x-dynamic-component
:component="$getFieldWrapperView()"
:field="$field"
>
<div
x-data="{ color: @js($state ?? '#000000') }"
class="flex items-center gap-3"
>
<input
type="color"
x-model="color"
x-on:change="$wire.set('{{ $getStatePath() }}', color)"
class="h-10 w-16 cursor-pointer rounded border border-gray-300"
/>
<input
type="text"
x-model="color"
x-on:change="$wire.set('{{ $getStatePath() }}', color)"
class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm"
placeholder="#000000"
/>
</div>
</x-dynamic-component>
x-dynamic-component :component="$getFieldWrapperView()" はFilamentの標準ラッパー(ラベル・エラーメッセージ表示を担う)を呼び出す決まり文句です。
$getState() / $setState() のJavaScript連携
Filamentはフォームの状態管理にLivewireを使っています。Bladeテンプレート内で使える主な関数:
| 関数 | 説明 |
|---|---|
$getState() | 現在のフィールド値を取得 |
$getStatePath() | Livewireのwireモデルパス(data.colorなど) |
$isDisabled() | 無効状態かどうか |
$isRequired() | 必須かどうか |
$getId() | フィールドのHTML id |
JavaScript側からLivewireの状態を更新するには$wire.set('{{ $getStatePath() }}', value)を使います。
カスタムPHPメソッドの追加
カスタムフィールドにも独自のメソッドを追加してFluentインターフェースを実現できます。
class ColorPicker extends Field
{
protected string $view = 'forms.components.color-picker';
protected bool $showAlphaChannel = false;
public function withAlpha(): static
{
$this->showAlphaChannel = true;
return $this;
}
public function getShowAlphaChannel(): bool
{
return $this->showAlphaChannel;
}
}
使用側:
ColorPicker::make('brand_color')
->label('ブランドカラー')
->withAlpha()
->required()
Bladeテンプレート側では$getShowAlphaChannel()で値を参照できます。
->postfix() と ->prefix() アドオン
TextInputなど標準フィールドには前後にテキストやアイコンを追加できます。カスタムフィールドにも同様の概念を持たせることができます。
TextInput::make('price')
->prefix('¥')
->postfix('円(税込)')
->numeric()
->minValue(0)
TextInput::make('domain')
->prefix('https://')
->postfix('.example.com')
これにより入力の文脈が明確になり、入力ミスを減らせます。
カスタムレイアウトコンポーネント:Fieldset・Tabs・Wizard
Filamentのフォームレイアウトはフィールドのグループ化に使うコンポーネントも豊富です。
Fieldset — フィールドのグループ化
use Filament\Forms\Components\Fieldset;
Fieldset::make('SEO設定')
->schema([
TextInput::make('meta_title')
->label('メタタイトル')
->maxLength(60),
Textarea::make('meta_description')
->label('メタディスクリプション')
->maxLength(160),
TextInput::make('canonical_url')
->label('Canonical URL')
->url(),
])
->columns(1)
Tabs — タブ切り替えフォーム
use Filament\Forms\Components\Tabs;
Tabs::make('記事設定')
->tabs([
Tabs\Tab::make('基本情報')
->icon('heroicon-o-document-text')
->schema([
TextInput::make('title')->required(),
RichEditor::make('body'),
]),
Tabs\Tab::make('SEO')
->icon('heroicon-o-magnifying-glass')
->schema([
TextInput::make('meta_title'),
Textarea::make('meta_description'),
]),
Tabs\Tab::make('公開設定')
->icon('heroicon-o-calendar')
->schema([
Select::make('status')
->options(['draft' => '下書き', 'published' => '公開中']),
DateTimePicker::make('published_at'),
]),
])
Wizard — ステップフォーム
複数ステップにわたる入力フローにはWizardが適しています。
use Filament\Forms\Components\Wizard;
Wizard::make([
Wizard\Step::make('基本情報')
->description('記事の基本情報を入力してください')
->icon('heroicon-o-pencil')
->schema([
TextInput::make('title')
->label('タイトル')
->required(),
Select::make('category_id')
->label('カテゴリ')
->relationship('category', 'name')
->required(),
]),
Wizard\Step::make('本文')
->description('記事の本文を入力してください')
->icon('heroicon-o-document-text')
->schema([
RichEditor::make('body')
->required()
->columnSpanFull(),
FileUpload::make('thumbnail')
->label('サムネイル')
->image(),
]),
Wizard\Step::make('公開設定')
->description('公開日時と設定を確認してください')
->icon('heroicon-o-paper-airplane')
->schema([
Select::make('status')
->options(['draft' => '下書き', 'published' => '公開'])
->default('draft'),
DateTimePicker::make('published_at')
->label('公開日時'),
TagsInput::make('tags')
->label('タグ'),
]),
])
->skippable() // ステップをスキップ可能にする
->skippable()を付けると各ステップへのナビゲーションが自由になります。付けない場合は順番通りに進む必要があります。
Section コンポーネントで折りたたみ可能なセクション
use Filament\Forms\Components\Section;
Section::make('詳細設定')
->description('任意の詳細設定項目です')
->icon('heroicon-o-cog-6-tooth')
->collapsible() // 折りたたみ可能
->collapsed() // デフォルトで折りたたまれた状態
->schema([
Toggle::make('allow_comments')->label('コメントを許可'),
Toggle::make('featured')->label('特集記事に設定'),
TextInput::make('sort_order')->numeric()->default(0),
])
Grid コンポーネントで列レイアウト
use Filament\Forms\Components\Grid;
Grid::make(3)
->schema([
TextInput::make('first_name')->label('名'),
TextInput::make('last_name')->label('姓'),
TextInput::make('phone')->label('電話番号'),
])
フィールド側で->columnSpan(2)を指定することで特定フィールドを複数列にまたがらせることもできます。
まとめ
Filamentのフォームコンポーネントシステムは高い拡張性を持っています。make:filament-form-fieldでカスタムフィールドを作り、Tabs・Wizard・Fieldsetなどのレイアウトコンポーネントを組み合わせることで、複雑なフォームもきれいに整理できます。次のエピソードではNotificationシステムとリアルタイム更新を解説します。