Select
A listbox of options in a popover, driven by an Ark collection.
Usage
import {
createListCollection,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@kanzo-tech/ui";const regions = createListCollection({
items: [
{ label: "Thornmarch", value: "Thornmarch" },
{ label: "Duskfen", value: "Duskfen" },
],
});The options live in a collection, not in the JSX. That is what lets the machine do
type-ahead, keep the highlighted index across a re-render, and know an item's label without
reading the DOM. createListCollection reads label and value off each item by default;
override with itemToString / itemToValue for any other shape.
Build the collection once at module scope, or memoise it — a new collection on every render resets the machine.
Type-ahead is not filtering. Typing dus jumps the highlight to "Duskfen"; the list still
shows every option. If you want the list to shrink as the user types, that is a
Combobox.
Which of the choice controls this should be — and where the thresholds against
NativeSelect and Combobox fall — is
answered once, on Controls.
Anatomy
Two more ship and neither is in the tree. SelectClearTrigger is a button that empties the
selection, for a select that is allowed to have no value. SelectContext is the render prop — a
function instead of elements, rendering no element of its own, which is how a caller reads the
selected items without useSelect().
Select
├── SelectTrigger renders Control > Trigger + ClearTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectGroupLabel
│ └── SelectItem
├── SelectSeparator
└── SelectEmptyWith field
A writ needs a hall's seal and four signatures.
Groups
Give the collection a groupBy and collection.group() hands back the [heading, items]
pairs, so the grouping is defined once with the data.
Multiple selection
multiple on the root, and the value becomes a list. SelectValue joins the selected labels for
you, and each SelectItem grows a check indicator. You do not need a Combobox for this.
Clearable
Sizes
Disabled
Keyboard
| Key | Does |
|---|---|
| ↓ / ↑ | On the closed trigger, opens the list with the first / last option highlighted; inside, moves the highlight. |
| Alt+↓ | Opens the list with nothing highlighted. |
| Home / End | Highlights the first / last option once the list is open. |
| Enter / Space | Opens the list from the trigger, and selects the highlighted option once it is open. |
| A–Z | Typeahead: highlights the next option whose text starts with what you have typed. |
| Esc | Closes the list without changing the value and returns focus to the trigger. |
On the closed trigger, ←/→, Home/End and
typeahead change the value outright without ever opening the list — single selection only, since
there is no one value to step through under multiple.
API Reference
Select
| Prop | Type | Default |
|---|---|---|
collection | ListCollection | — |
multiple | boolean | false |
lazyMount | boolean | true |
unmountOnExit | boolean | true |
Everything else passes through to Select.Root — value / defaultValue (always a string[],
single or multiple), onValueChange, name, form, disabled, invalid, readOnly,
positioning.
SelectTrigger
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "md" |
showClear | boolean | false |
Renders Select.Control and the chevron indicator for you.
SelectGroup
| Prop | Type | Default |
|---|---|---|
heading | string | React.ReactNode | — |
Select itself renders Select.HiddenSelect, so the value submits with a plain form, and
defaults lazyMount / unmountOnExit to true.