jackbayliss/livewire-select2
A Laravel Livewire component that integrates Select2 for searchable, AJAX-powered dropdowns, with server-side pagination, filtering, and simple Blade usage for dynamic select inputs.
jackbayliss/livewire-select2 is a thin wrapper that lets you use Select2 (search, tags, multi-select, remote data) on a Livewire-bound <select> without hand-writing the jQuery init/teardown glue. It is a UI convenience package, not a form or state package — Livewire still owns the value, Select2 owns the widget.
composer require jackbayliss/livewire-select2
Where to look first: the README's first example block is the whole integration surface. The package exposes a single component (a Blade-style tag plus a wire:model bind) and a small config file for default options. Publish the config only if you need to change global defaults:
php artisan vendor:publish --tag=livewire-select2-config
First use case — replace a plain select in an existing form with a searchable one and change nothing else in your component:
<livewire:select2
name="user_id"
wire:model="userId"
:options="$this->users" {{-- ['1' => 'Alice', '2' => 'Bob'] or a collection --}}
placeholder="Search users…"
/>
Because it is still a normal form input under the hood, validation, wire:model defer/live behaviour, and $this->validate() in your Livewire component all keep working unchanged. That is the main reason to reach for this over a bespoke Alpine combobox — it does not change your server-side contract.
Version note (1.3): this release only widens the framework constraint to include Laravel 13. There are no API changes; the upgrade is safe. If you pin jackbayliss/livewire-select2 in composer.json, a composer update on a Laravel 12 app will not pull anything unexpected — but check the resolved illuminate/support constraint if you are on an older LTS (9/10), where you may need to stay on 1.2.
Static option lists. Pass an associative array or collection; keys are submitted, values are displayed. Resolve options in a computed property (#[Computed]) so they are not re-queried on every render of a large form.
Remote/AJAX search. For large tables, do not hydrate thousands of options into the DOM. Feed Select2 an endpoint (a lightweight invokable controller or a dedicated Livewire method) that returns ['results' => [['id' => .., 'text' => ..]]], and pass the currently selected label in separately so the widget renders correctly before the first search. Keep the query scoped and paginated (->limit(20)) — Select2 fires on every keystroke.
Dependent selects (country → city). Listen for the parent's change in your Livewire component (updatedCountryId()), rebuild the child's option array, and dispatch a refresh event so the JS widget re-reads the options after the DOM morph:
public function updatedCountryId(): void
{
$this->cityId = null;
$this->dispatch('select2-refresh');
}
Multi-select / tags. A multiple select posts an array; type the Livewire property as array and validate with array + exists rules. For many-to-many (sync()), the array is exactly what you want — no mapping layer needed.
In modals and dynamically rendered forms. Because the widget is JS-initialised, anything that mounts markup after page load (a modal, a @if($showForm) block) needs the component to re-init. Prefer rendering the component inside the conditional rather than toggling display:none, and emit the refresh event from the action that opens the modal.
Integration tip: wrap the component in a small Blade partial of your own (<x-form.select2 ... />) that forwards name, wire:model, multiple, and placeholder. It gives you one place to add labelling/error markup (@error('user_id')) and keeps Select2-specific attributes out of your feature views.
window.jQuery exists before your app bundle runs.wire:navigate / SPA navigation. Full-page-navigated apps are fine; with wire:navigate the jQuery plugin state survives in a way the DOM no longer matches. Re-init on livewire:navigated (or disable wire:navigate for the page). This is the single most common "it works once then breaks" report.wire:model (not wire:model.live on every keystroke of an unrelated field), and keep the component's own attributes stable so Livewire can skip it.int and you pre-select with :value="123", the label may not appear until you interact. Cast consistently ((string) $id) or use ->mapWithKeys(fn ($m) => [(string) $m->id => $m->name]).[], which Livewire may coerce to null depending on your property default. Normalise in updated* hooks before validating, or your array rule will fail on empty submits..select2-container--default overrides, and use the container class attribute to scope them.<select> in the DOM with the right options, (2) did Select2 initialise (look for .select2-container), (3) does the hidden input update on change, (4) does the Livewire property update on the network payload. That sequence isolates JS vs. binding vs. server in four steps.placeholder, allowClear, theme). Everything beyond that is Select2's own options API — pass them through as an array rather than patching the package, so future Laravel-constraint releases (like 1.3) stay zero-effort upgrades.How can I help you explore Laravel packages today?