Multi-Select
The <x-wirekit::multi-select> component is a combobox that supports selecting multiple values. Selected items appear as removable pills inside the input area, and the dropdown filters options as the user types. It implements the WAI-ARIA 1.2 Combobox pattern with aria-multiselectable="true".
Basic Usage
With Preselected Values
The value prop accepts an array of option keys that should be pre-selected on load.
Error State
With Hint
Assign one or more roles to this user.
Width
Like all WireKit form components, the multi-select fills its container (w-full). Control the width via the parent element:
<div class="max-w-md">
<x-wirekit::multi-select label="Tags" name="tags" :options="$tags" />
</div>
See Input — Width for more layout examples (grid columns, mixed widths).
Options Format
The options prop accepts three formats — identical to the Combobox component:
{{-- Associative: key => label --}}
:options="['de' => 'Germany', 'fr' => 'France']"
{{-- List of strings (value === label) --}}
:options="['Apple', 'Banana', 'Cherry']"
{{-- List of arrays --}}
:options="[
['value' => 'de', 'label' => 'Germany'],
['value' => 'fr', 'label' => 'France'],
]"
Form Submission
Each selected value is submitted via a hidden <input type="hidden" name="name[]">. This means Laravel receives a standard array — no special parsing needed on the backend.
Optimistic UI
Pass the name of the Livewire method the control should call and the pill appears — or disappears — immediately, then confirms or undoes itself when the server answers:
<x-wirekit::multi-select
name="tags"
label="Tags"
:value="$tags"
:options="['php' => 'PHP', 'js' => 'JavaScript', 'css' => 'CSS']"
optimistic="saveTags"
/>
The method receives the full new selection as an array, not the value that changed:
public function saveTags(array $tags): void
{
$this->tags = $tags;
}
Load wirekit-optimistic.js alongside whichever bundle you already use — below it, in your layout:
@wirekitScripts
<script src="{{ asset('vendor/wirekit/wirekit-optimistic.js') }}"></script>
Try it
The demo below runs the real path: the change shows immediately, the outline says it is provisional, and the server's answer either confirms it silently or takes it back.
The <livewire:demos.…> wrapper above exists only on this site — it supplies the demo
methods so the page can show a real round trip. The block under it is what you write.
Focus stays on the option you clicked rather than returning to the filter, so you can keep picking. Removing a pill takes the same path as picking an option: it is the same server mutation, so it is undone the same way. The filter text is separate state and is never rolled back — an undo puts back the server's selection, never what you were typing.
What a screen reader hears Picking or removing announces once, hedged — "Saving" — so the change is audible as provisional. Confirmation is silent: what was announced is what happened. Only a deviation speaks a second time, which is what makes an undo recognizable as an undo.
Where the control sits in a form that already shows a validation message, the undo stays silent and leaves that message to speak: it tells you what to do, "could not save" does not.
An aborted request announces nothing at all — nothing was refused.
Focus stays exactly where you put it. An undo arrives on the server's schedule, and moving focus then would take you out of your place for a reason you could not predict.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string|null |
null |
Label text above the input |
hint |
string|null |
null |
Help text below the input |
error |
string|null |
null |
Error message (also reads from $errors) |
name |
string|null |
null |
Form field name (submits as name[]) |
id |
string|null |
auto-generated | Element id |
options |
array |
[] |
Options (associative, list, or array-of-arrays) |
value |
array |
[] |
Initially selected values |
optimistic |
string|null |
null |
Livewire method to call, showing the new selection before the server confirms it. Receives the full selection as an array. See Optimistic UI. |
optimisticArgs |
array |
[] |
Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to. |
placeholder |
string |
'Select...' |
Placeholder text in the filter input |
disabled |
bool |
false |
Disabled state |
ariaLabel |
string|null |
null |
Explicit aria-label for the internal combobox <input>. When unset, the component auto-derives a label from (in order) any passthrough aria-label attribute, then label, then placeholder, then name — so the internal input is never unlabelled. |
scope |
string|null |
null |
Scoped personalization key |
Accessibility
- Filter input:
role="combobox",aria-expanded,aria-controls,aria-autocomplete="list",aria-multiselectable="true" - Listbox:
role="listbox"witharia-multiselectable="true"and unique id linked viaaria-controls - Each option:
role="option",aria-selected="true"/"false" - Selected pills: each has a remove button with
aria-label="Remove {label}" - Empty state: "No results" message when filter produces no matches
- Error state:
aria-invalid="true"+aria-describedbylinking to error message - Selection changes announced via
aria-live="polite"region
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus to the multi-select trigger |
Enter / Space / ArrowDown |
Open the listbox |
ArrowUp / ArrowDown |
Move the active option |
Enter / Space |
Toggle the active option's selection |
Home / End |
Jump to first / last option |
| Type to search | Filter options (case-insensitive substring match) |
Backspace (in search field, empty input) |
Remove the last selected chip |
Escape |
Close the listbox |
Pitfalls
- Don't use multi-select for binary on/off groups. A row of
<x-wirekit::checkbox>is more discoverable. Multi-select is for moderate-to-large option lists where space is at a premium. - Don't set
:optionsto a list of plain strings. The component expects[{ value, label }]shape — strings render as objects withundefinedvalue and break form submit.
Design Tokens
| Token | Purpose |
|---|---|
--color-wk-bg-input |
Input area background |
--color-wk-bg-muted |
Selected pill background |
--color-wk-bg-elevated |
Dropdown panel background |
--color-wk-border-strong |
Default border color |
--color-wk-border-error |
Border color on error |
--color-wk-text |
Text color |
--color-wk-text-muted |
Placeholder and pill remove icon color |
--color-wk-accent |
Highlighted option background |
--color-wk-accent-fg |
Highlighted option text color |
--color-wk-ring |
Focus ring color |
--radius-wk-md |
Border radius for input and dropdown |
--radius-wk-sm |
Border radius for pills |
--shadow-wk-md |
Dropdown panel shadow |
--transition-wk-duration |
Open/close and highlight transitions |
Customization
Override defaults in config/wirekit.php:
'components' => [
'multi-select' => ['placeholder' => 'Choose...'],
],
Further Reading
- WAI-ARIA 1.2 Combobox Pattern — the authoring pattern this component implements
- MDN:
role="combobox" - MDN:
role="listbox" - MDN:
aria-multiselectable - MDN:
aria-selected