asignua / filament-clusters
Group several Filament 5 fields into one input row with a shared label, joined borders and a shared focus ring.
Requires
- php: ^8.3
- filament/filament: ^5.0
- illuminate/contracts: ^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.5|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Put several fields into one input row in Filament 5: one label, one bordered control, joined borders and a shared focus ring.
Address lines, name parts, ranges, money plus currency. Every child keeps its own state path and validation; the cluster only
draws them together and gathers their errors. A maintained Filament 5 successor of guava/filament-clusters.
Screenshots
Name, address, period, money plus currency, prefix and suffix and a two-row cluster: one label and one bordered control each:
Every child keeps its own validation; the cluster gathers the errors under one label:
Dark mode:
Requirements
- PHP 8.3+
- Laravel 12 or 13
- Filament 5
Installation
composer require asignua/filament-clusters php artisan filament:assets
Registering the plugin is optional (it only lists the plugin on the panel):
use Asignua\FilamentClusters\ClustersPlugin; $panel->plugin(ClustersPlugin::make());
Migrating from Guava: change the import to Asignua\FilamentClusters\Cluster. Note that an integer columns() now means "on every screen".
Usage
use Asignua\FilamentClusters\Cluster; use Filament\Forms\Components\TextInput; Cluster::make([ TextInput::make('first_name')->label('First name')->required(), TextInput::make('last_name')->label('Last name')->required(), ]) ->label('Name') ->hint('Legal name') ->helperText('As in the passport');
The children are saved as first_name / last_name, exactly as without the cluster.
Several clusters in one form
Cluster::make([...]) needs no name: each unnamed cluster gets a generated one from its child field names, stable between
Livewire requests, so any number of them can sit in one schema. A cluster owns no state, so the name only matters for DOM ids.
Columns and stacking
Cluster::make([...])->columns(4); // 4 equal columns on every screen Cluster::make([...])->columns(['default' => 1, 'sm' => 2]); // stacked on mobile Cluster::make([...])->stackBelow('md'); // one per line below md, a row from md up Cluster::make([...])->columns(1); // a vertical cluster
Per-child width: ->columnSpan(['default' => 3]) (a bare columnSpan(3) is Filament's lg-only span). Likewise ->columns(3) is ['default' => 3], not Filament's lg-only form.
Recipes
// Money + currency Cluster::make([ Select::make('currency')->options(['EUR' => 'EUR', 'USD' => 'USD'])->native(false)->default('EUR'), TextInput::make('amount')->numeric()->minValue(0)->columnSpan(['default' => 3]), ])->label('Price')->columns(4); // Range Cluster::make([ TextInput::make('min')->numeric()->prefix('from'), TextInput::make('max')->numeric()->prefix('to')->gte('min'), ])->label('Quantity'); // Address lines Cluster::make([ TextInput::make('street')->columnSpan(['default' => 1, 'sm' => 3]), TextInput::make('house')->columnSpan(['default' => 1, 'sm' => 1]), TextInput::make('zip'), TextInput::make('city')->columnSpan(['default' => 1, 'sm' => 3]), ])->label('Address')->columns(['default' => 1, 'sm' => 4]); // Date + time Cluster::make([DatePicker::make('day'), TimePicker::make('time')])->label('When')->stackBelow('sm');
Configuration
There is no config file. Restyle through CSS variables on .fi-cl: --fi-cl-radius, --fi-cl-divider, --fi-cl-ring,
--fi-cl-ring-focus, --fi-cl-ring-invalid. Dark mode follows Filament's .dark class.
Gotchas
- Children's own labels, hints and helper text are not drawn (the cluster supplies them). Their labels stay as screen-reader names.
- Multi-row clusters (more children than columns) draw a hairline on both sides of every later cell, so the first cell of a second row shows a short line at the outer edge.
- Don't override
isDisabled()-style logic from the children: the dimmed frame is shown when every child is disabled, the cluster itself is not disabled by it. - Only fields with a single-line frame (
fi-input-wrp) look right inside; textareas, toggles and editors do not. - Outside a panel (plain Livewire page) load the stylesheet with
FilamentAsset::getStyleHref('filament-clusters', 'asignua/filament-clusters').
Translations
English, Ukrainian, German, Spanish, French, Italian, Dutch, Polish, Brazilian Portuguese and Turkish under
filament-clusters::filament-clusters (the screen-reader name of a child). Publish with --tag=filament-clusters-translations.
AI agents
Laravel Boost guidelines ship in resources/boost/guidelines/core.blade.php.
Testing
composer install
composer test
composer analyse
vendor/bin/pint --test
Changelog
See CHANGELOG.md.
License
The MIT License (MIT). See LICENSE.md.



