EN
Getting started
63 · Forms

Combobox

A searchable select for long lists: single or multiple selection, removable chips, options with descriptions and groups, search through a Livewire method or a JSON URL, and adding values that are not in the list. Search ignores letter case and Turkish letters; the component is fully usable with a keyboard and screen readers.

PRO acun:combobox
The component requires the PRO JS entry (@acunsoft/acun-ui-pro): the import line in the CSS, JavaScript and build guide registers it automatically. If you start Alpine yourself, call registerCombobox(Alpine).
01

Single selection

Pass the options in options as a value => label array (or a collection via pluck()); value sets the initial selection. Click the box and start typing: search ignores letter case and Turkish letters (typing "sisli" finds "Şişli Branch", typing "igdir" finds "Iğdır Branch"), and the matching part is highlighted. The Down/Up arrows, Home/End, Enter and Escape operate the list from the keyboard; Enter does not submit the form.

LIVE EXAMPLE
<acun:combobox name="branch_id" label="Branch" value="2" :options="[
    1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch', 3 => 'Üsküdar Branch',
    4 => 'Çankaya Branch', 5 => 'Karşıyaka Branch', 6 => 'Nilüfer Branch', 7 => 'Ataşehir Branch',
    8 => 'Şişli Branch', 9 => 'Çamlıca Branch', 10 => 'Iğdır Branch',
]" />
PropValues
optionsvalue => label, or option arrays
valueSelected value (compared as a string)
02

Multiple selection

With multiple, selections appear inside the box as removable chips; when they do not fit, the box grows line by line. The list stays open after a selection so you can pick several in a row; clicking a selected option again removes it. Backspace in an empty box removes the last selection. max-chips limits how many chips are shown; the rest are counted, e.g. "+2" (hover to see their names).

LIVE EXAMPLE
<acun:combobox name="branches" label="Branches" multiple clearable :max-chips="3" :value="[1, 3, 4, 8, 9]" :options="[
    1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch', 3 => 'Üsküdar Branch',
    4 => 'Çankaya Branch', 5 => 'Karşıyaka Branch', 6 => 'Nilüfer Branch', 7 => 'Ataşehir Branch',
    8 => 'Şişli Branch', 9 => 'Çamlıca Branch', 10 => 'Iğdır Branch',
]" />
PropValues
multipleMultiple selection; the value is an array
max-chipsNumber of visible chips
clearable× that clears everything
03

Rich options and groups

Options can also be arrays: besides value and label they accept description (a second line; also searched), avatar (a round image URL), icon (a Heroicons name), disabled and group. Options with the same group value are gathered under a heading that sticks to the top while the list scrolls. Disabled options are visible but cannot be selected; the keyboard skips them. Nested arrays (['Managers' => [1 => 'Ayşe', …]]) become groups, and enum lists such as Status::cases() become options (a label() method, if present, provides the text); arrays with id and name fields and models are read too.

LIVE EXAMPLE
<acun:combobox name="assignee" label="Assignee" placeholder="Select a staff member" :options="[
    ['value' => 1, 'label' => 'Ayşe Yılmaz', 'description' => '[email protected]', 'icon' => 'shield-check', 'group' => 'Managers'],
    ['value' => 2, 'label' => 'Mehmet Kaya', 'description' => '[email protected]', 'icon' => 'shield-check', 'group' => 'Managers'],
    ['value' => 3, 'label' => 'Zeynep Arslan', 'description' => 'On leave', 'icon' => 'briefcase', 'group' => 'Sales team', 'disabled' => true],
    ['value' => 4, 'label' => 'Emre Demir', 'description' => '[email protected]', 'icon' => 'briefcase', 'group' => 'Sales team'],
    ['value' => 5, 'label' => 'Şule Öztürk', 'description' => '[email protected]', 'icon' => 'briefcase', 'group' => 'Sales team'],
    ['value' => 6, 'label' => 'Gökhan Çelik', 'description' => 'Accounting', 'icon' => 'calculator', 'group' => 'Head office'],
    ['value' => 7, 'label' => 'İsmail Işık', 'description' => 'Technical support', 'icon' => 'wrench-screwdriver', 'group' => 'Head office'],
]" />
PropValues
descriptionSecond line, searched
avatarImage URL
iconHeroicons name
disabledCannot be selected
groupGathers options under a heading
04

Search through a Livewire method

search is the name of a method on the Livewire component that contains the combobox. As the user types (after a 300 ms wait by default, changed with debounce), the method is called with the typed text and the options it returns are listed; while waiting, a spinner shows in the box, and "Loading…" appears if the list is empty. The list is also queried with an empty string each time it opens (unless min-chars is set). Selected values keep their labels even when the results change; selected-options provides the label of the selected value when the page first loads. The preview on this page uses a fake search in the browser instead of a server.

LIVE EXAMPLE
<acun:combobox
    wire:model="assigneeId"
    label="Assignee"
    search="searchStaff"
    :selected-options="Staff::whereKey($assigneeId)->pluck('name', 'id')"
    placeholder="Search staff"
    clearable
/>
PropValues
searchName of the Livewire method
selected-optionsLabels of the selected values
debounce300 (ms)
min-chars0
05

The search method

The method takes a single parameter: the text typed in the box (string $query; it may be empty). It returns the option list: [['value' => 1, 'label' => 'Ayşe Yılmaz', 'description' => …], …], a value => label array or ComboboxOptions::from(…) (which turns icon names into drawable paths). #[Json] runs the method without re-rendering the component and returns the result directly (#[Renderless] works too). When a selection is made, the box does not send a new search at the same time; the wire:model.live change goes in its own request and the page re-renders, and the list is queried again when the user types or reopens it. If order matters, return a list: an array with numeric keys (pluck()) becomes an object in JSON, and the browser sorts its keys. If you search with the Acun UI search infrastructure (the SearchManager driver and search columns added with searchColumn(); see the Search guide), the server also finds "Ayşe" from "ayse".

PHP
use Acun\Ui\Search\SearchField;
use Acun\Ui\Search\SearchManager;
use Acun\Ui\Search\SearchMode;
use Acun\Ui\Support\ComboboxOptions;
use Livewire\Attributes\Json;
use Livewire\Component;

class TicketForm extends Component
{
    public ?int $assigneeId = null;

    // <acun:combobox search="searchStaff" />: receives the typed text, returns the options.
    #[Json]
    public function searchStaff(string $query): array
    {
        // name_search and email_search: $table->searchColumn('name'), searchColumn('email') in the migration.
        $staff = app(SearchManager::class)->driver()
            ->apply(Staff::query(), $query, [SearchField::make('name', SearchMode::Contains), SearchField::make('email')])
            ->orderBy('name')
            ->limit(20)
            ->get();

        return ComboboxOptions::from($staff->map(fn (Staff $person) => [
            'value' => $person->id,
            'label' => $person->name,
            'description' => $person->email,
            'group' => $person->department,
        ]));
    }
}
06

Search through a JSON URL

On pages without Livewire, url is a JSON URL; the typed text is appended as ?q= (other parameters in the URL are kept). The response can be an option list, { "data": [...] } (API resources, paginators) or a value => label object; records shaped like { id, name } are read too. min-chars sets the minimum number of characters before searching; a stale response that arrives late does not overwrite newer results. The preview answers the URL in the browser.

LIVE EXAMPLE
<acun:combobox name="province_id" label="Province" url="/api/provinces" :min-chars="2" placeholder="Search provinces" clearable />
PropValues
urlJSON URL (?q= is appended)
min-charsMinimum characters before searching
07

Endpoint

The URL receives the typed text in the q parameter and returns the options. The session cookie is sent (same-origin), so routes behind the auth middleware work too.

PHP
use Illuminate\Http\Request;

Route::get('/api/provinces', function (Request $request) {
    return Province::query()
        ->where('name', 'like', '%'.$request->query('q').'%')
        ->orderBy('name')
        ->limit(20)
        ->get()
        ->map(fn (Province $province) => ['value' => $province->id, 'label' => $province->name]);
})->middleware('auth');
08

Adding new values

With creatable, text that has no exact match in the list gets an "Add “…”" row at the end of the list; differences in letter case and Turkish letters do not count as new (typing "WHOLESALE" finds "Wholesale" instead of adding it). Enter or a click selects the text as a value (value = text), and the root element dispatches a combobox-created event whose detail contains name and value.

LIVE EXAMPLE
<acun:combobox name="tags" label="Tags" multiple creatable :value="['urgent']" :options="[
    'urgent' => 'Urgent', 'priority' => 'Priority', 'wholesale' => 'Wholesale', 'business' => 'Business customer',
]" />
PropValues
creatableAdds text that is not in the list
combobox-createdEvent; detail: { name, value }
09

Saving an added value

The Livewire component listens for the event with #[On]; the parameters are the fields of the event's detail. If, after saving, you set the property to the new record's ID and update the option list, the combobox picks up both on the next render.

PHP
use Livewire\Attributes\On;

#[On('combobox-created')]
public function tagAdded(string $name, string $value): void
{
    if ($name === 'tags') {
        Tag::firstOrCreate(['name' => $value]);
    }
}
10

Clearing and limits

clearable adds a × on the right of the box that clears the selection; in single selection, Backspace in an empty box clears it too. max sets the maximum number of values in multiple selection: at the limit, a warning appears above the list and the other options become disabled, while selected ones can still be removed. :searchable="false" turns off typing, so short lists do not open the keyboard on phones. close-on-select sets whether the list closes after a selection (default: it closes in single selection and stays open in multiple selection).

LIVE EXAMPLE
<div class="grid gap-4 sm:grid-cols-2">
    <acun:combobox name="priority" label="Priority" value="2" clearable :searchable="false" :options="[1 => 'Low', 2 => 'Normal', 3 => 'High', 4 => 'Urgent']" />
    <acun:combobox name="categories" label="Categories (up to 3)" multiple :max="3" :value="['electronics', 'books']" :options="[
        'electronics' => 'Electronics', 'clothing' => 'Clothing', 'books' => 'Books', 'home' => 'Home & living', 'sports' => 'Sports',
    ]" />
</div>
PropValues
clearableClears with ×
maxMaximum selections (multiple)
searchabletrue | false
close-on-selectsingle: true · multiple: false
11

Classic form

When name is set without wire:model, the value is submitted through hidden fields: name in single selection, and name[] for each value in multiple selection (on the server, $request->input('tags', [])). required also triggers browser validation: the form is not submitted without a selection. In the preview, the values to be submitted appear below the form.

LIVE EXAMPLE
<form action="/orders" method="POST" class="space-y-4">
    @csrf
    <acun:combobox name="branch_id" label="Branch" required :options="[1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch', 3 => 'Çankaya Branch']" />
    <acun:combobox name="tags" label="Tags" multiple :options="['urgent' => 'Urgent', 'priority' => 'Priority', 'wholesale' => 'Wholesale']" />
    <acun:button type="submit">Save</acun:button>
</form>
PropValues
nameHidden field name (multiple: name[])
requiredSelection is required
12

Validation error and disabled

If there is a validation error for the field name (name or wire:model), the box gets a red border like acun:input and the message is shown below it; in multiple selection, tags.* errors are shown too. Without an error, the hint help text is shown. disabled locks the box; in a classic form, the value is not submitted.

LIVE EXAMPLE
<div class="grid gap-4 sm:grid-cols-2">
    <acun:combobox name="branch" label="Branch" required :options="[1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch']" />
    <acun:combobox name="locked_branch" label="Branch (locked)" value="2" disabled hint="Cannot be changed once the order is being prepared." :options="[1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch']" />
</div>
PropValues
hintShown when there is no error
disabledLocks; the value is not submitted
13

Inside a modal, drawer or table

The list is drawn above the page (with fixed positioning) and opens below the box, or above it when there is no room; so the overflow boundary of a modal, a drawer or a horizontally scrolling table does not clip it. The list follows the box as the page or modal scrolls. While the list is open, Escape closes only the list; the modal stays open. On phones, the list opens below the box at its width, and rows are at least 40 px tall.

LIVE EXAMPLE
<div class="flex flex-wrap gap-2">
    <acun:modal.trigger name="assign-ticket"><acun:button>Assign ticket</acun:button></acun:modal.trigger>
    <acun:drawer.trigger name="ticket-filters"><acun:button variant="white">Filters</acun:button></acun:drawer.trigger>
</div>

<acun:modal name="assign-ticket" title="Assign ticket">
    <div class="space-y-4">
        <acun:combobox name="agent" label="Agent" :options="[1 => 'Ayşe Yılmaz', 2 => 'Mehmet Kaya', 3 => 'Zeynep Arslan', 4 => 'Emre Demir', 5 => 'Şule Öztürk', 6 => 'Gökhan Çelik', 7 => 'İsmail Işık', 8 => 'Ömer Güneş']" />
        <acun:combobox name="ticket_tags" label="Tags" multiple :options="['billing' => 'Billing', 'return' => 'Return', 'shipping' => 'Shipping', 'technical' => 'Technical issue']" />
    </div>
    <acun:slot:footer>
        <acun:button variant="white" x-on:click="AcunUI.modal.close('assign-ticket')">Cancel</acun:button>
        <acun:button>Assign</acun:button>
    </acun:slot:footer>
</acun:modal>

<acun:drawer name="ticket-filters" title="Filters">
    <acun:combobox name="statuses" label="Status" multiple :options="['pending' => 'Pending', 'reviewing' => 'Under review', 'approved' => 'Approved', 'rejected' => 'Rejected']" />
</acun:drawer>

<div class="overflow-x-auto rounded-sm ring-1 ring-gray-200 dark:ring-zinc-700">
    <table class="w-full min-w-[34rem] text-left text-sm">
        <thead class="bg-gray-50 text-xs text-gray-500 dark:bg-zinc-800 dark:text-zinc-400">
            <tr><th class="px-3 py-2 font-medium">Ticket</th><th class="px-3 py-2 font-medium">Status</th><th class="px-3 py-2 font-medium">Agent</th></tr>
        </thead>
        <tbody class="text-gray-700 dark:text-zinc-300">
            <tr>
                <td class="px-3 py-2">#1042 · Return request</td>
                <td class="px-3 py-2">Pending</td>
                <td class="w-56 px-3 py-2"><acun:combobox name="agent_1042" aria-label="Agent" :options="[1 => 'Ayşe Yılmaz', 2 => 'Mehmet Kaya', 3 => 'Zeynep Arslan', 4 => 'Emre Demir', 5 => 'Şule Öztürk', 6 => 'Gökhan Çelik', 7 => 'İsmail Işık', 8 => 'Ömer Güneş']" /></td>
            </tr>
        </tbody>
    </table>
</div>
PropValues
acun:modal · acun:drawerOpens without clipping
overflow-x-autoThe table wrapper does not clip it
14

With Alpine

On Alpine pages, x-model binds to an outside variable: a value in single selection, an array in multiple selection. The values of value => label arrays are strings ('3'); since comparison is always done as strings, the number 3 selects the same option. Other attributes such as class and data-* are added to the outer wrapper. When the user changes the selection, the root element also dispatches a bubbling change event.

LIVE EXAMPLE
<div x-data="{ branch: '3', regions: [] }" class="space-y-3">
    <acun:combobox x-model="branch" label="Branch" :options="[1 => 'Kadıköy Branch', 2 => 'Beşiktaş Branch', 3 => 'Üsküdar Branch']" />
    <acun:combobox x-model="regions" label="Regions" multiple :options="[1 => 'Marmara', 2 => 'Aegean', 3 => 'Central Anatolia']" />
    <p class="text-sm text-gray-600 dark:text-zinc-400">branch: <span x-text="JSON.stringify(branch)"></span> · regions: <span x-text="JSON.stringify(regions)"></span></p>
</div>
PropValues
x-modelsingle: value · multiple: array
changeWhen the user makes a selection (bubbles)
15

With Livewire

wire:model, .live and .live.blur work (.blur alone does not send a request in Livewire 4); the number 3 coming from Livewire selects the option with the '3' key. The inner part of the box is protected with wire:ignore: when the component re-renders, the open list, the typed text and the selection stay intact. options, disabled and the validation error, on the other hand, are updated with each render; this is how dependent selects work, such as a district list that changes when a province is selected.

BLADE
<acun:combobox wire:model.live="provinceId" label="Province" :options="$provinces->pluck('name', 'id')" />
<acun:combobox wire:model.live.blur="districtId" label="District" :options="$districts->pluck('name', 'id')" :disabled="! $provinceId" />
<acun:combobox wire:model="staff" label="Staff" multiple :options="$staff->pluck('name', 'id')" />
PropValues
wire:model.live · .live.blur · .live.debounce.500ms
wire:keySet it when rendering in a loop
API

Props and slots

The values the component accepts.

PropDefaultDescription
labelnullLabel above the box
options[]value => label; option arrays; nested array (group); enum; records with id/name fields
valuenullInitial value (multiple: array); when wire:model / x-model is present, it takes precedence
multiplefalseMultiple selection; selections are removable chips
placeholderSelect…Text shown when nothing is selected
search-placeholderSearch…Hint in the empty box while the list is open
emptyNo resultsText shown when nothing matches
hintnullHelp text below, shown when there is no error
clearablefalse× that clears the selection (Backspace too in single selection)
creatablefalseAdds text that is not in the list; combobox-created event
searchabletruefalse: typing disabled (short lists)
maxnullmultiple: maximum selections; a warning is shown at the limit
max-chipsnullmultiple: number of visible chips, the rest as "+N"
close-on-selectsingle: true · multiple: falseWhether the list closes after a selection
searchnullLivewire method: fn (string $query): array (option list)
urlnullJSON URL; called with ?q=
debounce300Wait for remote search (ms)
min-chars0Minimum characters before a remote search
selected-options[]Labels of the selected values in remote lists
limit200Maximum options drawn at once in a local list
disabledfalseLocks; the hidden field is not submitted
requiredfalse* on the label; browser validation and aria-required
namenullClassic form field (multiple: name[]); error key
wire:model / x-model — single: value · multiple: array; .live and .live.blur work
Option keys — value, label, description, avatar, icon, disabled, group
Event: combobox-created — detail: { name, value }; in Livewire, #[On('combobox-created')]
Event: change — From the root element when the user changes the selection (bubbles)
Slots: id goes to the box itself (the label's for value), and aria-label (when there is no visible label) goes to the box and the list; other attributes such as class and data-* are added to the outer wrapper. Keyboard: ↓/↑ navigate (skipping disabled options), Home/End, PageUp/PageDown, Enter selects, Escape closes, Tab closes and moves on, Backspace removes the last selection.
Acun UI · ComboboxDetailed usage and examples