Skip to main content
WireKit
Copy for LLM

Select

A dropdown select component with label, error handling, and placeholder support.

Basic Usage

A labeled select

With Placeholder

With a placeholder

With Error

Error state

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.

Success / valid state

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.

Grouped + disabled options
<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:

Options from a slot

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.

Optimistic select — accepted, refused, and a slow answer

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's id
  • Error messages linked via aria-describedby and aria-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.live for huge option lists. Each keystroke during type-ahead would round-trip to the server. Use wire:model.blur or 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

Was this page helpful?

Thanks — that helps.

Voting requires cookies or local storage. What we store