Select
Single selects get a chevron drawn from --icon-chevron. Multiple
and sized selects keep the native list, with no icon.
| Class | Does what |
|---|---|
select | Full-width select with a chevron |
select-error | Error color, for when aria-invalid is not available |
Basic
<label class="label">
Environment
<select class="select">
<option>Development</option>
<option>Staging</option>
<option>Production</option>
</select>
</label>
<label>
Environment
<select>
<option>Development</option>
<option>Staging</option>
<option>Production</option>
</select>
</label>
Multiple
<select class="select" multiple size="3">
<option>One</option>
<option>Two</option>
<option>Three</option>
</select>
<select multiple size="3">
<option>One</option>
<option>Two</option>
<option>Three</option>
</select>
With Kobalte
alpha The class names and states here are stable. The look is not: these styles can change in a minor release, without a major version bump.
The native <select> above is settled. What follows is not: the
listbox styling and .menu-selected are new.
Kobalte's Select
is a button plus a listbox, not a native <select>. The Kobalte
layer gives the trigger the field styling above, and the listbox the dropdown
panel, so the same theme covers both.
<select class="select">
<option>Development</option>
<option>Production</option>
</select>
<select class="select">
<option>Development</option>
<option>Production</option>
</select>
import { Select } from "@kobalte/core/select";
<Select
options={["Development", "Staging", "Production"]}
value={env()}
onChange={setEnv}
itemComponent={props => (
<Select.Item item={props.item}>
<Select.ItemLabel>{props.item.rawValue}</Select.ItemLabel>
</Select.Item>
)}
>
<Select.Trigger>
<Select.Value>{state => state.selectedOption()}</Select.Value>
</Select.Trigger>
<Select.Portal>
<Select.Content>
<Select.Listbox />
</Select.Content>
</Select.Portal>
</Select>
<div>
<button aria-haspopup="listbox" aria-expanded="true" data-expanded style="margin-bottom: 0.25rem">
<span>Production</span>
</button>
<ul role="listbox">
<li role="option">Development</li>
<li role="option" data-highlighted>Staging</li>
<li role="option" data-selected>Production</li>
</ul>
</div>
Leave out Select.Icon: the field draws its own chevron, the same
one the native select gets.
Disabled
<select class="select" disabled>
<option>Production</option>
</select>
<select class="select" disabled>
<option>Production</option>
</select>
<Select disabled options={envs} value={env()} onChange={setEnv} itemComponent={Item}>
<Select.Trigger>
<Select.Value>{state => state.selectedOption()}</Select.Value>
</Select.Trigger>
</Select>
<button aria-haspopup="listbox" aria-expanded="false" disabled data-disabled>
<span>Production</span>
</button>
The trigger is a form field, so it carries the same bottom margin as an
.input. That margin is overridden in this demo because the
listbox sits inline here; in an app the content is portaled and positioned by
Kobalte's popper, which sets the gap with its gutter prop.
data-selected marks the chosen option and data-highlighted
the one under the pointer or caret, so they get different treatments:
.menu-selected and .menu-active.