Form controls
Select
One value from a list of five or more. The kit draws the list itself, in the theme's colors and type, and never hands it to the browser. It takes the same border, focus ring and error treatment as Input. For two to four short choices use a SegmentedControl, and see Choosing a control for the whole rule. Reach for Autocomplete when typing is the main way in, and MultiSelect for several values at once.
Basic
Pass the rows as options and bind the value.
proNever the browser's list
There is no native mode. A native select opens a list the operating system paints, in its own font and highlight color, and no stylesheet reaches it: on a dark page it opens white. So the trigger is a button and the panel is the kit's own, with the same keyboard model as a select. Arrow keys move, Enter picks, Escape closes, and typing a few letters jumps to a matching row.
In a form
name posts the value through a hidden input, exactly as a select would. onvaluechange fires once that input holds the new value, so a page that saves on change
can submit the form from it and post the value just picked.
A hidden input is barred from constraint validation, so required is announced through aria-required and enforced by the page and the server.
Groups
group collects a run of neighboring rows under a heading. An array that interleaves
two groups shows the heading twice, which is what the array says. placeholder shows while
nothing is chosen.
eu-west-1Icons
A row can carry a lucide icon before its label, and the chosen row carries a tick.
emailSearching the panel
searchable adds a search box, and the letters typed become query text instead of a
jump. Add it once a list passes about ten rows, as the thirteen tenants here do. keywords gives the matcher more than the label, so a row known by a machine name or a code can be found by typing
it. Matching ignores case and accents. Try GBX here.
(none)filter replaces the matcher, and filter={false} switches local filtering
off for a list that arrived already narrowed by a server query.
Under the keyboard, the search box turns its border the focus color, with no ring inside or around it, as the field does.
A trigger of your own
trigger fills the button rather than replacing it. The button stays, because it carries
the id, the label association and the ARIA, and a button nested in another button is not markup a browser
will focus.
Error state
Pick a role.
Described by text elsewhere
describedBy takes the ids of other elements that explain the control, separated by
spaces. A screen reader reads them after the control's own hint or error. Use it when the
explanation already sits on the page, such as a note above a form or the message a wrapping Field renders.
Shortening the window deletes older logs at the next sweep.
Applies to every stream.
Under a touch screen
Every option in the open list is 44px tall, as a Dropdown menu item is. A mouse keeps the shorter rows.
A list that would run past the side of the screen, or of the dialog or scrolling box it opens in, slides back inside it. In a container too short for it above and below, such as a one-field Modal, it opens below its field at its own height, and the container grows to hold it or scrolls down to it. The list keeps at least four rows in view, or all of them when it has fewer.
API
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | null | null | Bindable selected value. |
options | SelectOption[] | [] | The rows, as data. |
placeholder | string | no default | Shown while nothing is selected. |
searchable | boolean | false | Adds a search field inside the panel. |
filter | FilterFn<SelectOption> | false | no default | Replaces the default matcher, or false to switch local filtering off. |
trigger | Snippet<[{ selected, open }]> | no default | Fills the trigger. The button itself stays. |
label | string | no default | Label rendered above the control. |
hint | string | no default | Helper text below the control. An error replaces it. |
error | string | no default | Error message. Reddens the border. |
describedBy | string | no default | Ids of other elements that describe the control, separated by spaces, such as a wrapping Field's message. Read after the control's own hint or error. |
disabled | boolean | false | Disables the control and its posted value. |
required | boolean | false | Announced through aria-required. The page and the server enforce it. |
id | string | no default | DOM id of the trigger, useful for matching a Label. |
name | string | no default | Form name, posted through a hidden input. |
class | string | no default | Extra classes on the field wrapper. |
onvaluechange | (value: string) => void | no default | Fires once the hidden input holds the new value. |
noAI | boolean | false | Keep the value from an assistant: a FormAssist never reports it or fills it, and the control is marked data-no-ai for a browser agent. |
SelectOption
| Prop | Type | Default | Description |
|---|---|---|---|
value required | string | no default | The submitted value, and the key the row is rendered under. |
label required | string | no default | What the user reads. |
disabled | boolean | no default | Blocks this row alone. A disabled control blocks every row. |
icon | Component | no default | A lucide component before the label. |
keywords | string[] | no default | Extra text the default matcher searches, for a row known by a machine name. |
group | string | no default | Collects a run of neighboring rows under a heading. |