Dynamically showing and hiding fields in Filament
When building forms in Laravel with Filament, you'll often need to show or hide fields based on user input. A common scenario is a cascading select, where a "City" input appears only after a "State" is selected. While you could use custom JavaScript for this, Filament offers a much more robust and maintainable solution using its built-in visible()
and hidden()
methods, which you can easily combine with the $get
state access function. This article shows you how to eliminate unnecessary JavaScript and use Filament's built-in features for conditional form inputs. We'll focus on using Laravel's Eloquent, Filament, and visible()
with $get
.
Initially, developers might reach for JavaScript or Alpine.js (x-data
, x-show
, etc.) to manage conditional visibility. This, however, adds unnecessary overhead. It mixes Filament's declarative style with imperative JavaScript, leading to harder debugging and maintenance. Let's look at a better way!
Filament's visible()
and $get
: A Nice Combo
Filament's visible()
and hidden()
are used to set the visibility state of Filament form components, and can be made to depend on the state of other fields in your form. This is where $get
comes in handy. $get
lets you grab the current value of any field within the form. Combining these features provides precise control over field visibility without relying on custom JavaScript.
This magic is achieved by providing a callback to visible()
and hidden()
and then defining $get
as one of the callback's parameters. Let's see an example:
Building a Cascading State/City Select
Let's build a classic State/City dropdown where the City selection appears only after a State is chosen. Here's the streamlined Filament form code:
use Filament\Forms\Components\Select; use Filament\Forms\Get; use App\Models\State; use App\Models\City; // ... inside your Filament resource's form() method ... Select::make('state_id') ->label('State') ->options(State::all()->pluck('name', 'id')) ->searchable() ->live() ->afterStateUpdated(fn (Set $set) => $set('city_id', null)), // Clear city on state change Select::make('city_id') ->label('City') ->placeholder('Select a state first') ->options(fn (Get $get): array => City::where('state_id', $get('state_id'))->pluck('name', 'id')->toArray()) ->searchable() ->visible(fn (Get $get) => ! empty($get('state_id'))),
Key changes and explanations:
->visible(fn (Get $get) => ! empty($get('state_id')))
: This is on the "City" select. The closure uses$get
to access thestate_id
field's value. Ifstate_id
is empty (no state selected), the City field is hidden. If a state is selected, it becomes visible.->live()
: Added to the "State" select. This ensures immediate updates when the state selection changes, triggering the visibility check.afterStateUpdated
: clears the value on the city input.
Why This is the Preferred Method
This cleaner approach offers several advantages:
- Enhanced Readability: The logic is clear and directly tied to the field within Filament's declarative style.
- Improved Maintainability: Leveraging built-in features reduces bugs and simplifies future modifications.
- Centralized Logic: All dynamic behavior is within your Filament resource.
Conclusion
Filament's visible()
(and hidden()
) methods, combined with the $get
function, allow you to build dynamic and interactive forms efficiently, without resolving to using complex JavaScript. This approach yields cleaner, more maintainable, and more readable code. I prefer this approach because it keeps the logic within the Filament domain, which leads to improved cohesion and a better developer experience. Hopefully, like me, you like coding for the sanity of your future self, and that future fellow will thank you if you do these little things!