Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Livewire Select Laravel Package

asantibanez/livewire-select

Livewire Select is a Laravel Livewire component for building dependent, searchable select dropdowns. Chain selects to Eloquent models, dynamically filter options, and handle large datasets—ideal for country/city, category/subcategory, and other cascading form fields.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and nothing else — the package is a pure Livewire component, no config file or asset pipeline to publish for basic use:

composer require asantibanez/livewire-select

As of v2.3.0 the package targets Livewire 3 and Laravel 11 / 12. That means the version constraint is your upgrade lever: on Livewire 2 the latest tag will not install, so pin to the last 1.x release (composer require "asantibanez/livewire-select:^1.0") and stay there until you move the app to Livewire 3. On Livewire 3 + Laravel 11/12 you can take the current tag without ceremony.

Render the component from inside any existing Livewire component's Blade view. The two required pieces are a name (used to identify the selection in events) and an options array in the shape the package expects — a list of arrays with value and description keys:

<livewire:livewire-select
    name="country"
    :options="$countries"
    placeholder="Select a country"
/>
public function mount()
{
    $this->countries = Country::all()
        ->map(fn ($c) => ['value' => $c->id, 'description' => $c->name])
        ->toArray();
}

The component does not bind to wire:model. It communicates by emitting an inputSelected event with a payload of name and value. That is the whole mental model: render a select, listen for what it picked. The first use case to build on day one is a dependent pair — country → state — because it exercises the full loop (render, listen, update options, re-render) in about fifteen lines:

protected $listeners = ['inputSelected'];

public function inputSelected($payload)
{
    if ($payload['name'] === 'country') {
        $this->states = State::where('country_id', $payload['value'])
            ->get()
            ->map(fn ($s) => ['value' => $s->id, 'description' => $s->name])
            ->toArray();
    }
}

Implementation Patterns

Cascading selects. Keep all selection state in the parent Livewire component. The child select components are dumb renderers of whatever options you pass down. When the parent's inputSelected handler changes $states, Livewire re-renders the second <livewire:livewire-select> with new options automatically. For three-level chains (country → state → city), branch on $payload['name'] in a single handler rather than registering multiple listeners.

Resetting downstream selects. When the parent changes, the second select still displays the old label internally. Guard against stale values by passing :value="null" (or your reset sentinel) explicitly when you rebuild the options, and by clearing the corresponding parent property in the same handler. Treat value as a controlled prop you re-assert on every cascade.

Form integration. Because there is no HTML name attribute and no wire:model, the value never reaches $request on its own. Mirror it into a public property in the parent ($this->countryId = $payload['value']) and validate against that property. For edit forms, hydrate the initial value through the value prop and re-hydrate old() on validation failure — otherwise the select shows a placeholder while the rest of the form repopulates.

Searchable for large option sets. Add searchable when the list exceeds a couple dozen entries. Since filtering happens inside a nested Livewire component, keep the option list bounded (top 50 rows, indexed columns, no SELECT * on a million-row table) — a searchable select backed by a full table scan will feel worse than a plain one.

Reuse via your own wrapper. If you render the same select shape repeatedly, wrap it in a project Blade component (<x-country-select :selected="$countryId" />) so you centralise the value/description mapping and the name string. This is also the cleanest place to enforce a consistent option-mapping helper (->map(fn ($m) => ['value' => $m->id, 'description' => $m->label])).

Testing. Assert against the event contract, not the markup: Livewire::test(MyForm::class)->dispatch('inputSelected', ['name' => 'country', 'value' => 1])->assertSet('states', [...]). For the child itself, assertDispatched('inputSelected') on a direct test of the select component. On Livewire 3 these helpers (dispatch, assertDispatched) are the canonical API — if you have Livewire 2-era tests using emit/assertEmitted, update them at the same time you take v2.3.0.

Styling. Publish the views (php artisan vendor:publish --tag=livewire-select:views) and edit the Blade directly rather than fighting it with CSS overrides from outside — the markup is shallow enough that a few Tailwind class swaps are the fastest path to matching your design system.

Gotchas and Tips

  • v2.3.0 is Livewire-3-only. The release that added Laravel 11/12 support also formalises Livewire 3 as the baseline. If composer require resolves to a 1.x tag on your Livewire 2 app, that is Composer doing the right thing, not a broken package — pin deliberately and don't force --with-dependencies to drag the current tag down onto Livewire 2.
  • Filter events by name. Every select on the page emits the same inputSelected event. If you don't branch on $payload['name'], a city change will happily overwrite your country. This is the single most common bug with this package.
  • Options must be plain arrays. Passing an Eloquent Collection or a Collection of models silently renders nothing. Always ->map(...)->toArray() at the boundary.
  • description is the label key, value is the identifier. Mixing these up (or using label/id from a copied snippet) produces an empty dropdown with no error.
  • No wire:model, no name attribute. Don't try to submit the select with a plain form post, and don't expect $this->validate() to see it unless you mirrored the value into a property. wire:model exists in Livewire 3 but this component still doesn't expose it — don't assume the upgrade changed the contract.
  • Use wire:key inside loops. Rendering selects in a @foreach without a stable key causes Livewire to reuse component state across rows and show the wrong selection.
  • Nested components cost round-trips. Each select is its own Livewire component. A form with eight cascading selects is eight components; batch your option updates in the parent rather than fetching inside each child. Livewire 3's improved request batching helps, but it doesn't make per-child queries free.
  • Reset on cascade, always. A dependent select that keeps its old value after the parent changes is the classic symptom of forgetting to re-assert value in the parent handler.
  • Upgrading Livewire 2 → 3 alongside this bump. The public contract (props + inputSelected payload) is unchanged, so your parent handlers keep working. What does change is Livewire's event API: emit/emitUp/assertEmitted become dispatch/dispatch()->to()/assertDispatched. Any project code or tests touching those need updating in the same deploy, and it's worth re-testing cascades after the upgrade since component lifecycle timing shifted.
  • Extension point: publishing the views is the supported way to add things the props don't cover (option groups, icons, custom empty-state text). Avoid subclassing the component unless you also re-register the alias.
  • Debugging tip: when nothing appears, dump the exact array you're passing (@dump($options)) — nine times out of ten it's a Collection, a wrong key, or a null option list from a query that returned nothing.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr