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

PropTypeDefaultNotes
optionsSelectOption[]React firstThe options, as data. Replaces `items`.
onChange(value: string, option: SelectOption) => voidReact 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.
placementPlacement | PlacementAlias"bottomLeft"React firstWhere the listbox opens, from the same twelve names the overlays take.
itemsSelectItem[]Not in ReactOptions as data.
valuestringControlled value. Single-select in v1.
defaultValuestringUncontrolled initial value.
onValueChange(d: { value, item }) => voidNot in ReactReplaces v0's synthesised change event.
placeholderstring"Select an option"Shown while empty.
namestringSubmitted through the hidden <select>.
size"sm" | "md" | "lg""md"Not in ReactControl size.
labelReactNodeLabel, associated by id.
helperTextReactNodeHint below the field.
invalidbooleanfalseNot in ReactMarks the field invalid. v0 called this `error`.
errorMessageReactNodeReplaces helperText and wins aria-describedby.
fullWidthbooleantrueStretch 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

v0v1Notes
optionsitems
onChange(e) with e.target.valueonValueChange({ value, item })
errorinvalid
<Option> children ignored<Option> children build the collectionv0 destructured them into `_children` and never rendered them, which is why Table's page-size dropdown was always empty.
<Option> childrenoptionsReact only. One way to declare the options rather than two, and the one that survives being generated.
itemsoptions (React)
onValueChange({ value, item })onChange(value, option) (React)