Select
<x-bt-select />
A powerful select input supporting single and multiple selection, search filtering, remote data fetching, Eloquent collections, custom option mappings, autosave, and rich options with icons, avatars, and descriptions.
Customize via presets →

Shared presets

This component shares all presets and styling options with the x-bt-input component.

Basic Usage

Pass an associative array where keys are values and array values are labels.

+ Select status...
1<x-bt-select
2 label="Status"
3 :options="[
4 'draft' => 'Draft',
5 'review' => 'In review',
6 'published' => 'Published',
7 ]"
8 placeholder="Select status..."
9/>

Options Formats

The select accepts multiple data shapes. Everything is normalized internally.

Simple Array

Values and labels are the same string.

+ Select...
1<x-bt-select label="Fruit" :options="['Apple', 'Banana', 'Orange']" />

Associative Array

Keys become the stored value, array values become the display label.

+ Select...
1<x-bt-select
2 label="Status"
3 :options="['active' => 'Active', 'inactive' => 'Inactive', 'archived' => 'Archived']"
4/>

Array of Objects

Each item can have value, label, description, icon, and avatar keys.

+ Select...
1@php
2$objectOptions = [
3 ['value' => 1, 'label' => 'Alice', 'description' => 'Admin'],
4 ['value' => 2, 'label' => 'Bob', 'description' => 'Editor'],
5 ['value' => 3, 'label' => 'Carol', 'description' => 'Viewer'],
6];
7@endphp
8<x-bt-select label="User" :options="$objectOptions" />

Eloquent Collection

Use option-label, option-value, and option-description to map model fields.

+ Search a user...
1<x-bt-select
2 label="User"
3 :options="$users"
4 option-label="name"
5 option-value="id"
6 option-description="email"
7 placeholder="Search a user..."
8 searchable
9/>

Rich Options (Icons &amp; Avatars)

Options can include icon (Heroicon name, emoji, or raw SVG) and avatar (URL or emoji). Both render automatically in the dropdown.

+ Select country...
1<x-bt-select
2 label="Country Code"
3 :options="$richOptions"
4 placeholder="Select country..."
5/>

Slot-Based Options

Declare options directly in Blade using <x-bt-option> child components instead of a PHP array.

+ Select...
1<x-bt-select name="country" label="Country">
2 <x-bt-option value="AR" label="Argentina" />
3 <x-bt-option value="US" label="United States" />
4 <x-bt-option value="BR" label="Brazil" />
5</x-bt-select>

Mixing Prop + Slot Options

Slot options merge after prop options. If a slot option has the same value as a prop option, the slot option wins (here DE becomes "Deutschland").

+ Select...
1<x-bt-select name="mix" label="Mixed Sources" :options="['FR' => 'France', 'DE' => 'Germany']">
2 <x-bt-option value="ES" label="Spain" />
3 <x-bt-option value="DE" label="Deutschland" />
4</x-bt-select>

Multiple Selection

Enable :multiple="true" for multi-select. Selected values display as chips with a +N badge for overflow. Each chip has a remove button, and the dropdown shows checkboxes.

+ Select one or more tags...
1<x-bt-select
2 label="Tags"
3 :options="[
4 'frontend' => 'Frontend',
5 'backend' => 'Backend',
6 'devops' => 'DevOps',
7 'design' => 'Design',
8 ]"
9 :multiple="true"
10 placeholder="Select one or more tags..."
11/>

Searchable &amp; Clearable

Both are enabled by default. Disable them explicitly when needed.

+ Select...
+ Select...
1<x-bt-select
2 label="Not Searchable"
3 :options="['A' => 'Option A', 'B' => 'Option B', 'C' => 'Option C']"
4 :searchable="false"
5/>
6 
7<x-bt-select
8 label="Not Clearable"
9 :options="['A' => 'Option A', 'B' => 'Option B', 'C' => 'Option C']"
10 :clearable="false"
11/>

Sizes

+ Select...
+ Select...
+ Select...
+ Select...
+ Select...
1<x-bt-select xs label="Extra Small" :options="['A', 'B', 'C']" />
2<x-bt-select sm label="Small" :options="['A', 'B', 'C']" />
3<x-bt-select label="Medium (default)" :options="['A', 'B', 'C']" />
4<x-bt-select lg label="Large" :options="['A', 'B', 'C']" />
5<x-bt-select xl label="Extra Large" :options="['A', 'B', 'C']" />

Colors

The primary color (default) uses neutral gray with beartropy accents. Colors affect the dropdown background, option hover/active/selected states, chip styling, and border.

+ Select...
+ Select...
+ Select...
+ Select...
+ Select...
1<x-bt-select label="Default (primary)" :options="['A', 'B', 'C']" />
2<x-bt-select blue label="Blue" :options="['A', 'B', 'C']" />
3<x-bt-select red label="Red" :options="['A', 'B', 'C']" />
4<x-bt-select green label="Green" :options="['A', 'B', 'C']" />
5<x-bt-select purple label="Purple" :options="['A', 'B', 'C']" />

Dynamic colors

Set the color dynamically: <x-bt-select :color="$dynamicColor" />

Remote Data

Fetch options from an API endpoint. The component handles search debouncing, pagination, and infinite scroll automatically.

Remote Search

Enable remote and provide a remote-url. The component sends ?q=search&page=1&per_page=20 on each search/scroll.

+ Type to search...
1<x-bt-select
2 label="User (remote search)"
3 placeholder="Type to search..."
4 remote
5 searchable
6 remote-url="{{ route('api.users.select') }}"
7/>

Infinite Scroll

Set a small per-page value. When the user scrolls near the bottom of the dropdown, the next page is fetched automatically. A "Loading..." indicator appears during fetch.

+ Scroll to load more...
1<x-bt-select
2 label="Infinite Scroll (5 per page)"
3 placeholder="Scroll to load more..."
4 remote
5 searchable
6 :per-page="5"
7 remote-url="{{ route('api.users.select') }}"
8/>

Deferred Fetch

By default, remote selects fetch on page load. Use :defer="true" to delay the first fetch until the user opens the dropdown. Ideal when you have many remote selects on one page.

+ Click to load users...
1<x-bt-select
2 label="Deferred Load"
3 placeholder="Click to load users..."
4 remote
5 :defer="true"
6 remote-url="{{ route('api.users.select') }}"
7/>

Controller Example

Your endpoint receives q, page, and per_page query params. Return a JSON object with options (keyed by ID) and hasMore (boolean).

1use App\Models\User;
2use Illuminate\Http\Request;
3 
4class UserSelectController extends Controller
5{
6 public function index(Request $request)
7 {
8 $q = $request->input('q');
9 $page = (int) $request->input('page', 1);
10 $perPage = (int) $request->input('per_page', 20);
11 
12 $query = User::query();
13 
14 if ($q) {
15 $query->where(function ($sub) use ($q) {
16 $sub->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($q) . '%'])
17 ->orWhereRaw('LOWER(email) LIKE ?', ['%' . strtolower($q) . '%']);
18 });
19 }
20 
21 $total = $query->count();
22 
23 $options = $query
24 ->orderBy('name')
25 ->offset(($page - 1) * $perPage)
26 ->limit($perPage)
27 ->get()
28 ->mapWithKeys(fn ($user) => [
29 $user->id => [
30 'label' => $user->name,
31 'avatar' => $user->avatar,
32 'description' => $user->email,
33 ],
34 ]);
35 
36 return response()->json([
37 'options' => $options,
38 'hasMore' => ($page * $perPage) < $total,
39 ]);
40 }
41}

Expected JSON Response

Each key in options is the option value. The object must have at least a label. Optional fields: description, icon, avatar.

1{
2 "options": {
3 "1": { "label": "Alice Smith", "avatar": "https://...", "description": "alice@example.com" },
4 "2": { "label": "Bob Jones", "description": "bob@example.com" }
5 },
6 "hasMore": true
7}

Slots

Start Slot

Add custom content at the start of the trigger.

+ Select...
1<x-bt-select label="With Prefix" :options="['A', 'B', 'C']">
2 <x-slot:start>
3 <x-bt-button color="gray" soft>Prefix</x-bt-button>
4 </x-slot:start>
5</x-bt-select>

Before &amp; After Options

before-options renders above the options list. after-options renders below — and replaces the "No results" message when the filtered list is empty.

+ Select...
1<x-bt-select
2 label="With Dropdown Slots"
3 :options="[
4 'draft' => 'Draft',
5 'review' => 'In review',
6 'published' => 'Published',
7 ]"
8>
9 <x-slot:before-options>
10 <div class="p-2 text-sm text-gray-500">
11 <em>Recently used</em>
12 </div>
13 </x-slot:before-options>
14 <x-slot:after-options>
15 <div class="p-2 text-sm text-gray-500 space-x-3">
16 <em>Can't find what you need?</em>
17 <a href="#" class="text-blue-600 hover:underline">Create new</a>
18 </div>
19 </x-slot:after-options>
20</x-bt-select>

Select Inside Input

The Select can be placed inside an Input's start or end slot. Chrome (borders, shadows) is automatically stripped.

Phone Number Pattern

+ Code
1<x-bt-input label="Phone" placeholder="123-456-7890">
2 <x-slot:start>
3 <x-bt-select :options="['+1 US', '+44 UK', '+34 ES']" placeholder="Code" />
4 </x-slot:start>
5</x-bt-input>

Wider Dropdown (fit-trigger)

When inside a narrow slot, the dropdown may be too narrow. Use :fit-trigger="false" so the dropdown uses min-width instead of matching the trigger exactly.

+ Code
1<x-bt-input label="Phone (wider dropdown)" placeholder="123-456-7890">
2 <x-slot:start>
3 <x-bt-select
4 :options="['+1 United States', '+44 United Kingdom', '+34 Spain', '+49 Germany']"
5 placeholder="Code"
6 :fit-trigger="false"
7 />
8 </x-slot:start>
9</x-bt-input>

Livewire Integration

Deferred Binding

+ Select...
1<x-bt-select id="select-user-deferred" wire:model="userId" :options="$users" option-label="name" option-value="id" label="Deferred Binding" />

Live Binding

Use wire:model.live to update the Livewire property immediately on change.

+ Select...

Selected: none

1<x-bt-select id="select-live-category" wire:model.live="liveCategory" :options="['tech' => 'Technology', 'health' => 'Health', 'sports' => 'Sports']" label="Live Binding" />
2<p class="text-sm text-gray-500 mt-2">Selected: <strong>{{ $liveCategory ?? 'none' }}</strong></p>

Multiple with wire:model

For multiple selects, the Livewire property should be an array.

+ Select...
1<x-bt-select id="select-tags" wire:model="selectedTags" :multiple="true" :options="['laravel' => 'Laravel', 'vue' => 'Vue', 'react' => 'React', 'alpine' => 'Alpine']" label="Multiple with wire:model" />

Autosave

Automatically calls a Livewire method on every change. The trigger border shows state feedback: gray (saving), green (ok), red (error). Requires wire:model.

+ Select...
1<x-bt-select
2 id="select-autosave"
3 label="Default status"
4 :options="[
5 'draft' => 'Draft',
6 'review' => 'In review',
7 'published' => 'Published',
8 ]"
9 wire:model="defaultStatus"
10 :autosave="true"
11 autosave-method="savePreference"
12 autosave-key="defaultStatus"
13/>

Livewire Component

1public ?string $defaultStatus = null;
2 
3public function savePreference($value, $key)
4{
5 $this->toast()->success("Saved {$key} as {$value}");
6}

Validation Errors

Errors are automatically detected from the Laravel validation error bag using the wire:model name. You can also force an error with custom-error.

Auto Validation

Submit without selecting a role to see the error. The border turns red and the error message appears below.

+ Select...
1<x-bt-select id="select-role" wire:model="validatedRole" :options="['admin' => 'Admin', 'editor' => 'Editor', 'viewer' => 'Viewer']" label="Role" />
2<x-bt-button wire:click="submitRole" solid blue class="mt-3">Submit</x-bt-button>

Custom Error

Force an error state regardless of validation using custom-error.

+ Select...

Please select a valid status

1<x-bt-select
2 label="Status"
3 :options="['a' => 'Active', 'i' => 'Inactive']"
4 :custom-error="'Please select a valid status'"
5/>

Help Text

+ Select...

Choose your preferred category

+ Select...

Required field

1<x-bt-select label="Category" :options="['A', 'B', 'C']" help="Choose your preferred category" />
2<x-bt-select label="Priority" :options="['Low', 'Medium', 'High']" hint="Required field" />

Keyboard Navigation

Full keyboard support with ARIA attributes for screen readers.

Key Dropdown Closed Dropdown Open
Arrow DownOpen dropdownMove highlight down
Arrow UpOpen dropdownMove highlight up
EnterOpen dropdownSelect highlighted option
SpaceOpen dropdownType in search (if searchable)
Escape—Close dropdown
TabNormal tabNormal tab

Navigation wraps circularly. The highlighted option scrolls into view automatically. Mouse hover syncs with keyboard highlight. ARIA attributes (role="listbox", role="option", aria-selected) are included for screen reader support.

Real-World Example

A task creation form combining selects with a button.

+ Select team member...
+ Select priority...
1<div class="max-w-md space-y-4">
2 <x-bt-select
3 label="Assign to"
4 :options="$users"
5 option-label="name"
6 option-value="id"
7 placeholder="Select team member..."
8 searchable
9 icon="user-group"
10 />
11 <x-bt-select
12 label="Priority"
13 :options="['low' => 'Low', 'medium' => 'Medium', 'high' => 'High', 'critical' => 'Critical']"
14 placeholder="Select priority..."
15 />
16 <x-bt-button solid blue class="w-full">Create Task</x-bt-button>
17</div>

Slots

Slot Description
start
Custom content inside the trigger at the start.
end
Custom content inside the trigger at the end.
before-options
Content rendered at the top of the dropdown, after the search input.
after-options
Content rendered at the bottom of the dropdown. Replaces the empty message when no results match.

PROPS

Core props for the select component.

Prop Type Default Description
options
array|Collection|null null
Data source: simple arrays, associative arrays, arrays of objects, or Eloquent collections.
selected
mixed null
Initially selected value.
label
string|null null
Label text above the select.
placeholder
string|null Select...
Placeholder text when nothing is selected.
color
string|null config default
Color preset name.
size
string|null md
Size preset: xs, sm, md, lg, xl.
searchable
bool true
Show search input in the dropdown. For local options, filtering is client-side. For remote, the query is sent to the endpoint.
clearable
bool true
Show clear button when a value is selected.
multiple
bool false
Enable multiple selection with chips.
icon
string|null null
Trigger icon name.
initial-value
mixed null
Initial value for non-Livewire usage.
help
string|null null
Help text below the field.
hint
string|null null
Alias for help.
custom-error
mixed null
Custom error message (bypasses validation bag).
spinner
bool true
Show loading spinner during Livewire actions.
empty-message
string|null No options found
Text when the options list is empty.
per-page
int 15
Results per page for remote/pagination.
fit-trigger
bool true
Match dropdown width to trigger. Set false to allow the dropdown to be wider.

REMOTE PROPS

Props for remote data fetching.

Prop Type Default Description
remote
bool false
Enable remote data fetching via remote-url.
remote-url
string|null null
API endpoint URL. Receives ?q=&page=&per_page=.
defer
bool false
Defer remote fetch until dropdown opens for the first time.

OPTION MAPPING

Control which fields are extracted from each option object.

Prop Type Default Description
option-label
string label
Key for option label text. Fallbacks: name, text, value.
option-value
string value
Key for option value. Fallbacks: id, key.
option-description
string description
Key for option description. Fallbacks: desc, subtitle.
option-icon
string icon
Key for option icon (Heroicon name, emoji, SVG, or <img>).
option-avatar
string avatar
Key for option avatar (URL rendered as <img>, or text/emoji).

AUTOSAVE PROPS

Auto-save selection on change via Livewire. Requires wire:model.

Prop Type Default Description
autosave
bool false
Enable autosave mode.
autosave-method
string savePreference
Livewire method to call. Signature: method($value, $key).
autosave-key
string|null null
Key passed to the autosave method. Defaults to the wire:model name.
autosave-debounce
int 300
Debounce delay in milliseconds.
Code highlighting provided by Torchlight
Beartropy Logo

© 2026 Beartropy. All rights reserved.

Provided as-is, without warranty. Use at your own risk.