fabioguin/livewire-searchable-select
fabioguin/livewire-searchable-select adds a Livewire-powered searchable select component to Laravel apps, letting users filter long option lists as they type. It simplifies building responsive, searchable dropdowns in Blade/Livewire forms and improves UX when choosing from large datasets.
composer require fabioguin/livewire-searchable-select:^2.0.1
php artisan vendor:publish --provider="FabioGuin\LivewireSearchableSelect\LivewireSearchableSelectServiceProvider"
<livewire:searchable-select
wire:model="selectedUserId"
:options="$users"
searchable
placeholder="Search users..."
/>
Where to look first
searchTerm, the filtered option list, and the isSelected flag that v2.0.1 introduces guards around.x-model="$wire.searchTerm" as of this release). If you plan to customise styling, this is the file you'll touch.First use case to validate
Pick a single "assign user to team" field and confirm three behaviours end-to-end: typing filters the list, clicking the input again does not clear what you typed, and selecting an option closes the dropdown and clears the search term. Those three are precisely what v2.0.1 fixed, so they make a good smoke test before you roll the package out further.
Alpine note: on Livewire 3 nothing extra is required (Alpine ships with Livewire). On Livewire 2, ensure Alpine is loaded before Livewire's scripts — the x-model binding to $wire.searchTerm depends on the Livewire magic property being available when Alpine initialises.
Server-side search with debounce
Bind the search term on the component and let it filter server-side when the dataset is large:
public string $searchTerm = '';
public function updatedSearchTerm(): void
{
$this->options = User::query()
->where('name', 'like', "%{$this->searchTerm}%")
->limit(25)
->get()
->toArray();
}
Keep the debounce in the config rather than adding wire:model.live.debounce yourself — the component already owns the request cadence, and stacking a second debounce on searchTerm produces duplicate round-trips.
Form integration and validation
Treat the component like a native input for validation purposes: wire:model sets a plain public property, so $this->validate(['selectedUserId' => 'required|exists:users,id']) works unchanged. For long forms, use wire:model (deferred) on the selection and allow searchTerm to update live — the search term is transient and shouldn't trigger validation.
Resetting the field programmatically
After a successful submit, clear both the selection and the search state:
$this->reset('selectedUserId');
$this->dispatch('searchable-select-reset');
Calling $wire.set('searchTerm', '') from Alpine is also valid, but prefer the server-side reset so the dropdown's isSelected state is recalculated in the same render.
Custom option rendering
Publish the views and extend the option partial to include avatars, secondary text, or badges. Keep the wire:key on each option tied to the option's ID so Livewire can diff filtered lists correctly when the search term changes.
Testing
Livewire::test(SearchableSelect::class)->set('searchTerm', 'jane')->assertSee('Jane Doe') covers the filtering path. Add an assertion that searchTerm survives a click on an already-selected option — that's the regression v2.0.1 fixed, and it's cheap to lock in.
searchTerm into a local Alpine variable. The old bug came from two sources of truth. v2.0.1 binds x-model directly to $wire.searchTerm; if you have custom markup that does x-data="{ searchTerm: @entangle('searchTerm') }", remove it or you reintroduce the desync the release just eliminated.isSelected guard when merging custom click handlers. The fix that stops the search term being wiped when you click an input with an active selection lives in the click handler. If you've forked that handler to add analytics or custom events, re-apply the isSelected check after upgrading.$wire.searchTerm shows the authoritative server value. If the input's visible text and that value disagree, you have an Alpine binding problem (usually a stale published view), not a Livewire one. The Livewire devtools payload inspector confirms whether searchTerm is being sent on each request.wire:ignore in modals is a trap. Wrapping the component in wire:ignore to protect it from modal re-renders also blocks the $wire.searchTerm updates this release depends on. Use wire:key scoped to the modal instance instead.$wire magic property in x-model requires a Livewire version that exposes it (Livewire 3, or Livewire 2 with the Alpine integration in place). A mismatched pair produces a silently empty search box rather than an error.How can I help you explore Laravel packages today?