daguilarm/livewire-combobox
A lightweight Livewire component for searchable select and dropdown inputs. Add an autocomplete-style combobox to Laravel forms with server-side searching and dynamic options, without building custom JavaScript.
Install and pin. The package is archived (last release 2021-05-12), so pin the version explicitly rather than trusting ^1.0:
composer require daguilarm/livewire-combobox
Check composer.json for its Livewire constraint before installing — it targets Livewire 2.x era and will not work with Livewire 3 without a fork. If you're on Laravel 10/11 with Livewire 3, treat this as a reference implementation, not a dependency.
Where to look first.
README.md — the only reliable spec for the trait/config surface, since there is no maintained docs site.src/ — the trait that gets applied to your Livewire component, and the abstract/contract for the config array. This is where the "levels" contract lives.config/ (if published) — defaults for level keys, property names, and the view used to render each <select>.Minimal first use case — two dependent selects (e.g. Country → City):
php artisan vendor:publish if a config or view tag is exposed.use the package trait on it.updated* lifecycle hooks for you.<select wire:model> per level bound to the trait's public properties.wire:model to the trait's property for that level. Changing the parent fires updated{Property} and the trait reloads the child's options.The value proposition over hand-rolling updated() hooks is infinite levels: you describe the hierarchy once and every parent/child reload is handled uniformly, including clearing stale downstream selections.
Hierarchy definition. The idiomatic pattern is a declarative level list — one entry per select, each describing its property name, its options source (query/model), and which parent property it listens to. Register this once per component (in mount() or a getLevels() method). Adding a third or fourth level should be a config change only, not new lifecycle code.
Option loading. Keep the option-loading callable returning a keyed collection (id => label) so Blade can use @foreach without reshaping. Guard against empty parents — when the parent is null, the child must return an empty collection, not all rows. This is the single most common bug in cascading select implementations.
Resetting downstream state. When level n changes, every level n+1…N must be nulled before options reload. The package handles this via its lifecycle hooks, but your form/validation layer must tolerate the properties briefly being null. If you snapshot the model before save, do it after the last updated hook settles, or use wire:model.defer on the leaf level and validate on submit.
Form integration. Bind the component's public properties to your Eloquent model in the parent Livewire component, or use the combobox component as a child and emit the final selected chain upward via $this->emitUp() / dispatch. For a classic address form this is: country_id, state_id, city_id — validate city_id last and use Rule::exists() scoped to state_id so a stale value from before a parent change can't be submitted.
Reusable "lookup" components. The cleanest day-to-day pattern is to wrap the combobox in your own <x-combobox> Blade component with typed props (:levels, :model), so application code never talks to the package trait directly. When the package eventually breaks against a newer Livewire, you swap the internals in one place.
Seeder/demo pattern. Because the config is an array, the same component instance can serve different hierarchies (geo, product categories, org units) by passing a different level definition. Build one canonical GeoCombobox and one CategoryCombobox rather than a generic everything-component — the generic one becomes unmaintainable fast.
Testing. The reload happens server-side on an updated hook, so Livewire::test(MyCombobox::class)->set('country_id', 1)->assertSet('state_id', null)->assertSee('California')-style assertions work without a browser. This is the main reason to prefer the trait over a pure Alpine.js cascading approach.
Archived — this is the headline risk. No Livewire 3 compatibility, no PHP 8.2+ guarantees, no security patches. Before adopting, run the full test matrix against your actual Laravel/Livewire versions. A reasonable strategy: use it as a design reference for your own HasCascadingSelects trait (the logic is small — level config + updated hooks + nulling), or fork it and vendor it under packages/. Do not leave it as a floating constraint in composer.json.
Livewire 2 lifecycle assumptions. The package predates Livewire 3's changes to wire:model (deferred by default), $emitUp deprecation, and lifecycle hook naming. If you're on Livewire 3 and trying to port it, the reload hooks are the thing that will silently no-op — nothing errors, the child select just never updates. Add an explicit test asserting the child options change when the parent is set.
wire:model vs wire:model.defer. With deferred binding the parent change fires in a batch; with live binding it fires per keystroke/selection. For multi-level cascades you almost always want wire:model (live) on parent selects and defer only on the final leaf, otherwise you get a request per level-1 change. Verify which the package's view partial uses — changing it in your own Blade is fine, but if the trait depends on the live hook firing, defer will break it.
N+1 on options. Each level reload typically issues a query. With three levels and a hot form that's three queries per user interaction. Cache option lists keyed by (level, parent_id) in Cache::remember with a short TTL, or eager-load with ->toBase()/select('id','name'). Profile before optimizing — but on a public-facing form with high traffic this package will otherwise dominate your query count.
Empty-parent behavior. Most "dynamic select" bugs are the child query returning everything when parent_id is null. Assert in a test: assertSee('Select a state', false) when no country is chosen. The package may or may not guard this — assume it doesn't and add the guard in your option callable.
Config caching. If levels are defined in a config array, php artisan config:cache will freeze them. Level definitions that reference Eloquent models (rather than class-strings/IDs) will fail to serialize. Keep model references lazy (class name + method) or define levels in code, not config.
Extension points are thin. Because it's an archived single-trait package, the practical extension points are (a) your own Blade partial overriding the package's select markup and (b) subclassing/overriding the trait's option-resolution method. Everything else means editing vendor/. Fork if you need the latter.
Licensing is fine. MIT, so forking and vendoring is explicitly permitted — that's your realistic path forward in 2024+, not composer require.
How can I help you explore Laravel packages today?