amabk/livewire-search-select

Reusable Livewire searchable dropdown component.
280 1
Install
composer require amabk/livewire-search-select
Latest Version:v1.2.3
PHP:^8.1
License:MIT
Last Updated:Aug 26, 2025
Links: GitHub  ·  Packagist
Maintainer: AMABK

Livewire Search Select

alt text A powerful and customizable Livewire component for searchable select dropdowns. Supports multi-field labels, concatenation, and emits Livewire events for reactive parent handling.


Usage

1. Installation

Install via Composer:

composer require amabk/livewire-search-select

2. Basic Usage

Add the component to any Blade view:

Single-select

<livewire:search-select
    model-class="\App\Models\User"
    :label-fields="['name','email']"
    :search-fields="['name','email']"
    :concat-fields="true"
    placeholder="Search by name or email…"
    emit-event="userSelected"
    :selected-id="$userId ?? null"
    :initial-load="5"
    input-class="border-gray-300 rounded-md shadow-sm focus-within:ring-1 focus-within:ring-blue-500 focus-within:border-blue-500"
/>

Multi-select (chips inside field)

<livewire:search-select
    model-class="\App\Models\User"
    :label-fields="['name','email']"
    :search-fields="['name','email']"
    :concat-fields="true"
    :multiple="true"
    :selected-ids="$preselected ?? []"
    :initial-load="8"
    placeholder="Search and add users…"
    input-class="border-gray-300 rounded-md shadow-sm focus-within:ring-1 focus-within:ring-blue-500 focus-within:border-blue-500"
/>

Custom separators

{{-- Display label uses " | ", search concatenation still uses space (more forgiving) --}}
<livewire:search-select
  model-class="\App\Models\User"
  :label-fields="['name','email']"
  :search-fields="['name','email']"
  :concat-fields="true"
  label-separator=" | "
  search-separator=" "
/>
{{-- Both label and search use the same separator --}}
<livewire:search-select
  model-class="\App\Models\User"
  :label-fields="['first_name','last_name','email']"
  :search-fields="['first_name','last_name','email']"
  :concat-fields="true"
  label-separator=" · "
  search-separator=" · "
/>
Prop Type Default Notes
modelClass string Eloquent model FQCN, e.g. \App\Models\User
labelFields array<string> | string ['email'] Fields used to render the visible label
searchFields array<string> ['name'] Fields used for LIKE search
concatFields bool false If true, search uses CONCAT_WS(searchSeparator, …)
labelSeparator string ' ' Separator when rendering labels (display only)
searchSeparator string ' ' Separator used in CONCAT_WS when concatFields=true
orderBy {field:string, direction:'asc' | 'desc'} { field: 'id', direction: 'asc' } Sort for initial and searched results
limit int 10 Max rows for search results
initialLoad int 0 Prefetch N items on focus when search is empty
placeholder string 'Search...' Hidden when chips exist (multi)
emitEvent string | null null Event name to dispatch on change
inputClass string '' Extra classes on the wrapper (the chip field)
optionClass string '' Extra classes on the dropdown <ul>
multiple bool false Enable multi-select
selectedId int | null null Preselect for single-select
selectedIds int[] [] Preselect for multi-select

3. Listening for Selection Events

When a user selects an option, the component emits a Livewire event (named by your emit-event prop) to the parent component. You can listen for it and update your parent Livewire component’s data accordingly.

Example Parent Component:

class UserForm extends Component
{
    public $selectedUserIds = [];

    #[On('usersSelected')] 
    // single: receives id or '' when cleared
    // multi: receives array<int> of ids
    public function usersSelected($userIds)
    {
        $this->selectedUserIds = $userIds;
    }

    public function render()
    {
        return view('livewire.user-form');
    }
}

In your Blade view:

<livewire:search-select
    :model-class="\App\Models\User::class"
    label-fields="name,email"
    label-separator=" ["
    label-suffix="]"
    :multiple="true"
    emit-event="usersSelected"
/>
Selected User IDs: {{ implode(', ', $selectedUserIds) }}

4. Customizing Appearance

You can pass custom CSS classes to the input and the options list:

<livewire:search-select
    :model-class="\App\Models\Product::class"
    :label-fields="['title', 'sku']"
    label-separator=" | SKU: "
    :multiple="true"
    emit-event="productsChosen"
    input-class="border-blue-500"
    option-class="max-h-48"
    placeholder="Find a product..."
/>


5. Setting a Default Selected Option

To set a default selection:

<livewire:search-select
    :model-class="\App\Models\Category::class"
    label-fields="name"
    :selected-id="[5, 9]"
    :multiple="true"
    emit-event="categoriesChosen"
/>

This will preselect the items with id = 5 and id = 9 and show their labels in the input.


6. Troubleshooting & Tips

  • No results found: Ensure your model and label fields are correct and records exist.
  • Alpine/Livewire conflicts: Alpine.js is auto-reinitialized after Livewire updates, but if you experience issues, check your Alpine.js version.
  • Emitted event not caught? Ensure your parent Livewire component is listening for the same event name as the emit-event you specify.

7. Development

  • PSR-4 namespace: AMABK\LivewireSearchSelect\ → src/
  • Component class: AMABK\LivewireSearchSelect\SearchSelect
  • View: resources/views/search-select.blade.php (Loaded as view('livewire-search-select::search-select'))

8. Example: User Dropdown in a Form

<form wire:submit.prevent="save">
    <livewire:search-select
        :model-class="\App\Models\User::class"
        :label-fields="['name', 'email']"
        :search-fields="['name', 'email', 'address']"
        label-separator=", "
        label-suffix=" (active)"
        :multiple="true"
        emit-event="userSelected"
        placeholder="Search for a user by name or email..."
    />

    <button type="submit" class="btn btn-primary">Submit</button>
</form>
// Parent Component (UserForm.php)
public $userIds = [];
protected $listeners = ['userSelected'];

public function userSelected($userIds)
{
    $this->userIds = $userIds;
}


10. Advanced: Use With Any Model & Any Label Format

<livewire:search-select
    :model-class="\App\Models\Country::class"
    :label-fields="['country_name', 'country_code']"
    :search-fields="['name', 'currency_code']"
    label-separator=" - "
    label-suffix=""
    :multiple="true"
    emit-event="countriesSelected"
    placeholder="Search country..."
/>


For advanced use, extend the component in your own package. For issues, open a GitHub issue or PR!

License

MIT © AMABK