williamug / searchable-select
A beautiful, searchable dropdown component for Laravel Livewire 3 & 4 applications. Built with Alpine.js and Tailwind CSS - no external dependencies required!
Fund package maintenance!
Williamug
Installs: 3
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:Blade
pkg:composer/williamug/searchable-select
Requires
- php: ^8.4
- illuminate/contracts: ^11.0||^12.0
- livewire/livewire: ^3.0||^4.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
README
A beautiful, searchable dropdown component for Laravel Livewire 3 & 4 applications. Built with Alpine.js and Tailwind CSS - no external dependencies required!
Live Demo & Interactive Examples
Explore all features with live, interactive examples:
- Basic Select - Simple searchable dropdown
- Multi-Select - Select multiple options with tags
- Grouped Options - Organize by categories
- API Integration - Dynamic loading from endpoints
- Advanced Examples - All features combined
👉 Run the demo locally with Docker or PHP
Screenshots
Features
- Real-time search - Client-side filtering as you type
- Multi-select support - Select multiple options at once
- Ajax/API integration - Fetch options dynamically from endpoints
- Grouped options - Organize options into categories
- Clear button - Easily clear selections
- Dark mode support - Automatically adapts to your theme
- Accessible - Keyboard navigation and ARIA attributes
- Livewire 3 & 4 compatible - Works with both versions
- Responsive - Mobile-friendly design
- Disabled state - Conditional disabling support
- Flexible data - Works with models, arrays, collections
- Dependent dropdowns - Perfect for cascading selects
- Customizable - Override styles with Tailwind classes
- Zero config - Works out of the box
Requirements
- PHP 8.1+
- Laravel 9.x, 10.x, 11.x, or 12.x
- Livewire 3.x or 4.x
- Tailwind CSS 3.x+
- Alpine.js (bundled with Livewire)
Installation
Install via Composer:
composer require williamug/searchable-select
Run the installation command:
php artisan install:searchable-select
That's it! The component will be copied to resources/views/components/searchable-select.blade.php
Force reinstall
If you want to overwrite an existing installation:
php artisan install:searchable-select --force
Quick Start
Basic Usage
Livewire Component:
<?php namespace App\Livewire; use App\Models\Country; use Livewire\Component; class ContactForm extends Component { public $countries; public $country_id; public function mount() { $this->countries = Country::orderBy('name')->get(); } public function render() { return view('livewire.contact-form'); } }
Blade View:
<x-searchable-select :options="$countries" wire-model="country_id" :selected-value="$country_id" placeholder="Select Country" search-placeholder="Search countries..." />
Dependent/Cascading Dropdowns
If you have related dropdowns (e.g. Country → Region → City), you can easily update the options based on the selected value of the parent dropdown.
Livewire Component:
<?php namespace App\Livewire; use App\Models\{Country, Region, City}; use Livewire\Component; class LocationSelector extends Component { public $countries, $regions = [], $cities = []; public $country_id, $region_id, $city_id; public function mount() { $this->countries = Country::orderBy('name')->get(); } public function updatedCountryId() { $this->regions = Region::where('country_id', $this->country_id) ->orderBy('name')->get(); $this->region_id = null; $this->city_id = null; $this->cities = []; } public function updatedRegionId() { $this->cities = City::where('region_id', $this->region_id) ->orderBy('name')->get(); $this->city_id = null; } public function render() { return view('livewire.location-selector'); } }
Blade View:
<div class="grid grid-cols-3 gap-4"> <!-- Country --> <div> <label>Country</label> <x-searchable-select :options="$countries" wire-model="country_id" :selected-value="$country_id" placeholder="Select Country" /> </div> <!-- Region --> <div> <label>Region</label> <x-searchable-select :options="$regions" wire-model="region_id" :selected-value="$region_id" :placeholder="empty($regions) ? 'First select a country' : 'Select Region'" :disabled="!$country_id" /> </div> <!-- City --> <div> <label>City</label> <x-searchable-select :options="$cities" wire-model="city_id" :selected-value="$city_id" :placeholder="empty($cities) ? 'First select a region' : 'Select City'" :disabled="!$region_id" /> </div> </div>
Component Props
| Prop | Type | Default | Description |
|---|---|---|---|
options |
Array/Collection | [] |
List of options to display |
wireModel |
String | '' |
Livewire property to bind (required) |
selectedValue |
Mixed | null |
Currently selected value |
placeholder |
String | 'Select option' |
Placeholder when nothing selected |
searchPlaceholder |
String | 'Search...' |
Search input placeholder |
disabled |
Boolean | false |
Disable the dropdown |
emptyMessage |
String | 'No options available' |
Message when options is empty |
optionValue |
String | 'id' |
Key for option values |
optionLabel |
String | 'name' |
Key for option labels |
multiple |
Boolean | false |
Enable multi-select mode |
clearable |
Boolean | true |
Show clear button when value selected |
apiUrl |
String | null |
API endpoint for dynamic options |
apiSearchParam |
String | 'search' |
Query parameter name for API search |
grouped |
Boolean | false |
Enable grouped options |
groupLabel |
String | 'label' |
Key for group labels |
groupOptions |
String | 'options' |
Key for group options array |
Advanced Examples
Multi-Select
Select multiple options at once:
public $selected_countries = []; // Array for multiple selections
<x-searchable-select :options="$countries" wire-model="selected_countries" :selected-value="$selected_countries" :multiple="true" placeholder="Select Countries" />
With Clear Button
The clear button is enabled by default. Disable it if needed:
<x-searchable-select :options="$countries" wire-model="country_id" :selected-value="$country_id" :clearable="false" />
Grouped Options
Organize options into groups:
public $locations = [ [ 'label' => 'North America', 'options' => [ ['id' => 1, 'name' => 'United States'], ['id' => 2, 'name' => 'Canada'], ['id' => 3, 'name' => 'Mexico'], ] ], [ 'label' => 'Europe', 'options' => [ ['id' => 4, 'name' => 'United Kingdom'], ['id' => 5, 'name' => 'France'], ['id' => 6, 'name' => 'Germany'], ] ], ];
<x-searchable-select :options="$locations" wire-model="country_id" :selected-value="$country_id" :grouped="true" placeholder="Select Country" />
Ajax/API Integration
Fetch options dynamically from an API endpoint:
<x-searchable-select :options="[]" wire-model="user_id" :selected-value="$user_id" api-url="{{ route('api.users.search') }}" api-search-param="q" placeholder="Search users..." />
Your API endpoint should return JSON:
// routes/api.php Route::get('/users/search', function (Request $request) { $users = User::where('name', 'like', '%' . $request->q . '%') ->limit(20) ->get(['id', 'name']); return response()->json(['data' => $users]); });
Multi-Select with API
Combine multiple selection with API search:
<x-searchable-select :options="[]" wire-model="selected_users" :selected-value="$selected_users" :multiple="true" api-url="{{ route('api.users.search') }}" placeholder="Select Team Members" />
With Arrays
public $statuses = [ ['id' => 'draft', 'name' => 'Draft'], ['id' => 'published', 'name' => 'Published'], ['id' => 'archived', 'name' => 'Archived'], ];
<x-searchable-select :options="$statuses" wire-model="status" :selected-value="$status" />
Custom Keys
public $products; // Has 'sku' and 'product_name' fields
<x-searchable-select :options="$products" wire-model="product_sku" :selected-value="$product_sku" option-value="sku" option-label="product_name" />
Custom Styling
<x-searchable-select :options="$countries" wire-model="country_id" :selected-value="$country_id" class="border-2 border-blue-500 rounded-xl" />
With Validation
protected $rules = [ 'country_id' => 'required|exists:countries,id', 'city_id' => 'required|exists:cities,id', ];
<x-searchable-select :options="$countries" wire-model="country_id" :selected-value="$country_id" /> @error('country_id') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
Customization
Tailwind Configuration
Make sure your tailwind.config.js includes the component path:
export default { content: [ './resources/views/**/*.blade.php', './resources/views/components/**/*.blade.php', ], }
Create Specialized Components
Create a dedicated component for common use cases:
resources/views/components/country-select.blade.php:
<x-searchable-select :options="\App\Models\Country::orderBy('name')->get()" wire-model="{{ $wireModel }}" :selected-value="$selectedValue" placeholder="Select Country" search-placeholder="Search countries..." {{ $attributes }} />
Usage:
<x-country-select wire-model="country_id" :selected-value="$country_id" />
Troubleshooting
Dropdown doesn't open
- Verify Alpine.js is loaded (part of Livewire 3+)
- Check browser console for JavaScript errors
- Ensure no JavaScript conflicts
Selected value not displaying
- Verify
selectedValuematches an option value - Check
optionValueprop matches your data structure - Ensure the value exists in options array
Styling issues
- Run
npm run buildto compile Tailwind - Verify component path in
tailwind.config.js - Check for CSS conflicts
Performance
- < 1,000 options: Client-side filtering (default) works great
- > 1,000 options: Consider server-side search with
wire:model.live.debounce - Very large datasets: Implement pagination or lazy loading
Testing
The package includes comprehensive tests. Run them with:
composer test
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure tests pass (
composer test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
The MIT License (MIT). Please see License File for more information.
Credits
- William Asaba
- Built with Laravel
- Powered by Livewire
- Styled with Tailwind CSS
- Enhanced with Alpine.js
Links
If this package helped you, please ⭐ star the repository!

.png)