vitisstudio/filament-choosable-chips
A Filament form component that lets users select one or more options as clickable chips, offering a compact visual alternative to dropdowns and integrating cleanly with Filament forms.
Requirements check first. This package is hard-gated to PHP 8.4+ and Filament v5 (filament/forms ^5.0). Confirm both before you spend time on it — a v3/v4 Filament app or a PHP 8.2/8.3 runtime can't use it at all, and there's no backport path.
composer require vitisstudio/filament-choosable-chips
Where to look first (in this order):
Select/ToggleButtons, so anything you already know about options(), defaultColor(), disableOptionWhen(), and gridDirection() transfers directly. Confirm the exact FQCN and namespace from the package's src/ or README before wiring it into a form.First use case: a small, enumerable choice field where you want the options visible at a glance rather than hidden behind a dropdown — status, priority, category, a small tag set, or a compact filter bar in a custom Livewire form.
use VitisStudio\FilamentChoosableChips\Chips; // verify FQCN against the package
Chips::make('status')
->options([
'draft' => 'Draft',
'review' => 'In review',
'published' => 'Published',
])
->colors([
'draft' => 'gray',
'review' => 'warning',
'published' => 'success',
])
->icons([
'draft' => 'heroicon-o-pencil',
'review' => 'heroicon-o-eye',
'published' => 'heroicon-o-check-circle',
]);
That's radio semantics by default — state stays a single scalar, so it drops into an existing string column with no migration.
Enum-backed options are the happy path. Because colors(), icons(), and descriptions() are all keyed by option value, you can drive the whole field from one enum and keep labels, colors, and icons colocated instead of drifting across three arrays:
Chips::make('status')
->options(Status::class)
->colors(collect(Status::cases())->mapWithKeys(fn ($c) => [$c->value => $c->color()])->all())
->icons(collect(Status::cases())->mapWithKeys(fn ($c) => [$c->value => $c->icon()])->all())
->required();
Choose single vs. multiple deliberately, up front. Default is radio semantics (scalar state); ->multiple() flips to checkbox semantics (array state). Treat this as a schema decision, not a styling decision — it dictates the column type, the Eloquent cast, and the shape your ->afterStateUpdated() callbacks receive.
Chips::make('tags')
->options(Tag::class)
->multiple()
->checkSelected();
Use the checkmark as your "selected" affordance, explicitly. ->checkSelected() turns on the selected check, and it auto-enables only when no per-option icons are set. The moment you add ->icons([...]), you lose the checkmark unless you call checkSelected() yourself. Be deterministic: call it every time you rely on it.
Gate options with disableOptionWhen() for capability, not for security. It's ideal for "you can't pick archived while the record is open" or "this option is out of stock" UX. Keep the matching authorize/policy check server-side — the field also derives an in validation rule from the enabled options, which is a strong safety net, but it's still a render-time snapshot.
Compact layouts. size(), defaultColor(), and gridDirection() are the levers for dense contexts: table filters, repeater rows, action modals, and sidebars. A horizontal grid of small chips is a good substitute for a long Select in a filter panel.
Testing. In Livewire/Filament form tests, fillForm() takes a scalar for the default mode and an array once ->multiple() is set. Write assertions against the resulting model attribute in the shape your cast produces — most test failures with this field come from shape mismatch, not from the field itself.
scalar (default) vs array (->multiple()) changes what your cast, migration, and reactive callbacks must handle. Switching a field from single to multiple after ship is a data migration (backfilling scalars into JSON arrays), not a one-line change. Decide once.in rule tracks the enabled options. Options filtered out by disableOptionWhen() — especially when the closure depends on another field's state — can produce validation errors that look like the field is broken. The same applies to options that only exist after a dependent field changes.checkSelected() is conditional on there being no per-option icons. Easy to "lose" the checkmark after a cosmetic tweak.null/[] into the form state. If the field is ->required(), expect validation to fire after a user dismisses the last chip — that's correct behaviour, but it surprises people in filter contexts where "cleared" should be valid. Drop required() there.'1' vs 1 vs an enum case will fail silently and fall back to defaultColor() with no error. If chips render but colours/icons don't apply, check key coercion first — this is the fastest debugging win.->live() integration with dependent fields. Assume it's a subset of Select's surface until proven otherwise.multiple() matches the column type; wrong chip styling → check map key types; validation failing on a valid-looking option → check disableOptionWhen() closures and dynamic option lists.colors()/icons()/descriptions() maps and a Blade view override over forking the field class. Those are the stable, cheap surface; a fork will need re-merging on every Filament v5 minor bump.How can I help you explore Laravel packages today?