Select
A dropdown select component with label, error handling, and placeholder support.
Basic Usage
With Placeholder
With Error
Please select a role.
Errors from Laravel's validation bag are shown automatically when name matches a field.
With Success
Confirm a valid selection with a green border and optional message — the mirror of the error state, but valid (no aria-invalid). error wins when both are set.
Plan confirmed
Option Groups & Disabled Options
The :options array accepts three shapes you can mix freely. Nest an array under a label key to render an <optgroup>; give an option an array value with 'disabled' => true to disable it.
<x-wirekit::select name="destination" :options="[
// Nested array under a label key → <optgroup label="Europe">
'Europe' => ['ber' => 'Berlin', 'par' => 'Paris'],
// An option whose value is ['label' => ..., 'disabled' => true] is disabled
'Asia' => ['tyo' => 'Tokyo', 'sin' => ['label' => 'Singapore (sold out)', 'disabled' => true]],
]" />
Slot Options
For more control, use the default slot instead of the :options prop:
Width
Like all WireKit form components, the select fills its container (w-full). Control the width via the parent element:
<div class="max-w-xs">
<x-wirekit::select label="Country" name="country" :options="$countries" />
</div>
See Input — Width for more layout examples (grid columns, mixed widths).
On Mobile
Because this component is a real <select>, the open dropdown is drawn by the operating system, not by WireKit — so on a phone or tablet it appears as the platform's native picker (the iOS wheel sheet, the Android dropdown). That is deliberate: the native picker is the most accessible, most familiar, and most touch-friendly option list a mobile user can get, and it needs no extra JavaScript.
One thing to know when testing: a desktop browser's device-emulation mode (e.g. DevTools' responsive view) draws that OS popup against the real window rather than the emulated viewport, so the option list can look small or mis-placed there. That is an artifact of the emulator — on a real device the picker opens correctly. Test native pickers on an actual phone, or trust the emulator only for the trigger, not the OS popup.
If you instead need a dropdown that renders consistently across every device and is fully themeable — with search, custom option markup, or multi-value selection — reach for <x-wirekit::combobox> (single value, client-side filtering) or <x-wirekit::multi-select> (multiple values). Both are custom listboxes built on Alpine + Floating UI, so their open panel is WireKit-styled and positions identically everywhere.
Optimistic UI
Pass the name of the Livewire method this component should call and the change appears immediately, then confirms or undoes itself when the server answers:
<x-wirekit::select
name="plan"
:value="$plan"
optimistic="choosePlan"
>
<option value="free">Free</option>
<option value="pro">Pro</option>
</x-wirekit::select>
Load wirekit-optimistic.js alongside whichever bundle you already use — it is a separate file so applications that do not use it pay nothing for it:
@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.
A second change while one is in flight is refused rather than queued, and that matters more here than on a toggle: queued, the final selection would be whichever answer arrived last, which is network timing.
What a screen reader hears is the same for every component that supports this, and it is written out in full — one hedged announcement at the change, silence on confirmation, one more only if the server refuses, and focus that never moves — in Optimistic UI — the announcement contract. That page is also where the boundary is stated: this covers mutations, not sorting, filtering or pagination.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
optimistic |
string|null |
null |
Livewire method to call, showing the new value before the server confirms it. See Optimistic UI. |
optimisticArgs |
array |
[] |
Extra arguments appended to the optimistic action call, after the new value — the row this control belongs to. |
label |
string|null | null |
Label text above the select |
hideLabel |
bool | false |
Render the label visually hidden (sr-only) but keep it for assistive tech — for a compact field in a toolbar / header |
hint |
string|null | null |
Help text below the select |
reserveMessage |
bool | false |
Keep the message line's height even when there is no message — see Fields in a row |
error |
string|null | null |
Error message (overrides $errors bag) |
success |
string|bool|null | null |
Valid-state confirmation — string shows a green message, true shows just the green border. error wins. |
size |
string | 'md' |
sm, md, lg |
placeholder |
string|null | null |
Placeholder option text |
value |
string|null | null |
Pre-selected option key. Compared as a string, so 1 and '1' both match |
options |
array | [] |
Options. Flat (['de' => 'Germany']), grouped (['Europe' => ['de' => 'Germany']] → <optgroup>), or per-option attrs (['de' => ['label' => 'Germany', 'disabled' => true]]). Mix freely. |
scope |
string|null | null |
Scoped personalization name |
Accessibility
- Built on the native
<select>element — full browser keyboard support out of the box - Label is a proper
<label for="...">matched to the select'sid - Error messages linked via
aria-describedbyandaria-invalid="true" - Hint text linked via
aria-describedby - Native
<select>supports arrow keys, Home/End, type-to-search, and screen reader announcements by default
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Move focus to the select |
Space / Enter / ArrowDown |
Open the option list |
ArrowUp / ArrowDown |
Move between options |
Home / End |
Jump to first / last option |
| Type a character | Type-ahead — focus next option whose label starts with that character |
Escape |
Close the option list without changing the value |
Pitfalls
- Don't pass an array of strings to
:options. The component expects[{ value, label }]shape. Plain strings render as<option>{string}</option>with no value, breaking form submit. - Don't use
wire:model.livefor huge option lists. Each keystroke during type-ahead would round-trip to the server. Usewire:model.bluror convert to<x-wirekit::combobox>with client-side filtering.
Design Tokens
| Token | Used for |
|---|---|
--font-wk-sans |
Body font family |
--font-wk-letter-spacing |
Letter spacing |
--text-wk-sm / --text-wk-md / --text-wk-lg |
Font size per size prop |
--color-wk-text |
Selected option text |
--color-wk-text-muted / --color-wk-text-subtle |
Placeholder + chevron |
--color-wk-bg-input |
Select background |
--color-wk-border-strong |
Default border |
--color-wk-border-strong-hover |
Hover border |
--color-wk-border-error |
Error-state border |
--color-wk-danger / --color-wk-danger-text |
Error message |
--color-wk-ring / --color-wk-ring-offset |
Focus ring color + offset |
--ring-wk-width / --ring-wk-offset |
Focus ring geometry |
--border-wk-width |
Border width |
--radius-wk-sm / --radius-wk-md |
Border radius |
--shadow-wk-sm |
Subtle shadow |
--size-wk-sm / --size-wk-md / --size-wk-lg |
Control height per size prop |
--padding-wk-x-sm / --padding-wk-x-md / --padding-wk-x-lg |
Horizontal padding |
--opacity-wk-disabled |
Disabled visual weight |
--transition-wk-duration / --transition-wk-easing |
Hover / focus transition |
Config Defaults
The defaults live in config/wirekit.php under components.select. Override them globally:
'components' => [
'select' => ['size' => 'lg'], // change default size
],
See Also
- Inline Edit — edit this value in place, with an explicit confirm step
Further Reading
- MDN:
<select>element - MDN:
<option>element - MDN: Styling
<select> - WebAIM: Creating Accessible Forms — form accessibility primer