Forms
Select
The trigger is a real `<button role="combobox">`. v0 used `<input readOnly role="combobox">`, which is the ARIA pattern for a typeahead combobox and wrong for a plain select.
data-scope="select"
What you get for free
- Arrow-key navigation and Home/End
- Typeahead
- A managed highlight that keyboard and mouse cannot disagree about
- A real hidden <select>, so plain form submission works
Usage
<Select
label="Country"
options={[{ value: "ng", label: "Nigeria" }]}
onChange={(value, option) => setCountry(option.label)}
/><Select label="Country" :items="items" @update:value="v => (country = v)" />
<!-- or declaratively -->
<Select label="Country">
<Option value="ng">Nigeria</Option>
</Select><Select label="Country" {items} bind:value={country} />
<!-- Svelte snippets cannot be read as text, so Option takes a label prop -->
<Select label="Country">
<Option value="ng" label="Nigeria" />
</Select><ck-select label="Country" [items]="items" [(value)]="country" />
<!-- or declaratively -->
<ck-select label="Country">
<ck-option value="ng" label="Nigeria" />
</ck-select>Props
| Prop | Type | Default | Notes |
|---|---|---|---|
options | SelectOption[] | — | React firstThe options, as data. Replaces `items`. |
onChange | (value: string, option: SelectOption) => void | — | React firstThe value AND the option it came from — the second is what a consumer usually wants and would otherwise have to look up again. |
size | "small" | "middle" | "large" | "middle" | React firstEmits `data-size` in that vocabulary, so v2 carries its own rules. |
status | "error" | "warning" | — | React firstColours the control, and `error` also marks the trigger `aria-invalid`. `warning` is presentation only — there is no ARIA state for it. Use `errorMessage` for something that should be read out. |
placement | Placement | PlacementAlias | "bottomLeft" | React firstWhere the listbox opens, from the same twelve names the overlays take. |
items | SelectItem[] | — | Not in ReactOptions as data. |
value | string | — | Controlled value. Single-select in v1. |
defaultValue | string | — | Uncontrolled initial value. |
onValueChange | (d: { value, item }) => void | — | Not in ReactReplaces v0's synthesised change event. |
placeholder | string | "Select an option" | Shown while empty. |
name | string | — | Submitted through the hidden <select>. |
size | "sm" | "md" | "lg" | "md" | Not in ReactControl size. |
label | ReactNode | — | Label, associated by id. |
helperText | ReactNode | — | Hint below the field. |
invalid | boolean | false | Not in ReactMarks the field invalid. v0 called this `error`. |
errorMessage | ReactNode | — | Replaces helperText and wins aria-describedby. |
fullWidth | boolean | true | Stretch to 100%. |
Prop names are identical in all four frameworks. Only two-way binding differs, and each framework uses its own idiom over the same underlying prop. The marked rows are the exception, and a temporary one, in both directions: React first exists in React while the other three adapters catch up, and Not in React is the prop it replaced, still live everywhere else.
Migrating from react-ui-toolkit
| v0 | v1 | Notes |
|---|---|---|
options | items | |
onChange(e) with e.target.value | onValueChange({ value, item }) | |
error | invalid | |
<Option> children ignored | <Option> children build the collection | v0 destructured them into `_children` and never rendered them, which is why Table's page-size dropdown was always empty. |
<Option> children | options | React only. One way to declare the options rather than two, and the one that survives being generated. |
items | options (React) | |
onValueChange({ value, item }) | onChange(value, option) (React) |