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'))),
Databases in VS Code? Get DevDb

Key changes and explanations:

  • ->visible(fn (Get $get) => ! empty($get('state_id'))): This is on the "City" select. The closure uses $get to access the state_id field's value. If state_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!

Wanna chat about what you just read, or anything at all? Click here to tweet at me on 𝕏