All components (204)
Select
Choice · Select · .amset-select
What it is and when to use it
A dropdown to choose one option from a list. Closed, it shows what is chosen (or the placeholder); open, the list with a mark on the chosen option. The options are passed as data (options, each with key and label), not as children: that way none is left without text. The list appears in the browser's top layer (popover), above any z-index and also inside a dialog, and closes when you click outside.
When to use it
- One option among many that do not need to be seen at once: the province, the metal of a ring, the shipping method.
- In a form: it has a label, a hint (
hint), an error (error) and a required asterisk (required) like the rest of the fields; withname, the value travels in a native form. shortLabelon an option when the whole label does not fit in the box: "+34" instead of "+34 · Spain".valueandonChangeto control it (it receives the chosenkey);placeholderchanges the default "Choose an option".groupon an option puts it under a title ("Europe", "Asia"): the options of one group must come together inoptions.
When not to
- Few options that are better seen without opening anything:
RadioGroup. - A long list where you need to search by typing:
SearchField. - Choosing several:
MultiSelect. - A list that on a phone is better chosen with the system picker:
native, which draws a real<select>(it loses icons and two lines per option). - Language and currency:
LocalePicker, which already writes them correctly.
Keyboard: Tab reaches the trigger. Closed, Enter, Space or the up and down arrows open the list, and focus moves to it. Open, the arrow keys move, Home and End go to the first and the last, typing jumps to the option that starts with those letters, Enter or Space choose and close, and Escape closes: in both cases focus returns to the trigger. Disabled options are skipped and cannot be chosen.
Accessibility: the trigger is named with the label and what is chosen; the hint and the error are read as its description. hiddenLabel leaves the label for the screen reader only: it is for a field inside another that already explains itself (the prefix of a phone), never to save space. readOnly allows focusing and reading it, but the list does not open. With loading, the trigger carries aria-busy and a loading ring. With a finger, each option in the list is at least 44 px tall.
Field box: three sizes and three variants (outline, soft, ghost), with the same hover and the same ring as a TextField. The ring stays on while the list is open, and the arrow rotates.
Fine-tuning: it shares the field variables, --amset-field-* (height, padding, radius, background, border and font), so it looks the same as a TextField next to it; the arrow measures --amset-select-arrow. The list is adjusted with amset-select__panel.
What it looks like
<Select
label="Plan"
placeholder="Choose an option"
options={[
{ key: 'basico', label: 'Basic' },
{ key: 'pro', label: 'Professional', description: 'Priority support' },
{ key: 'empresa', label: 'Company' },
]}
/>Props
Import it with import { Select } from '@amset/ui'. Required props are marked.
| Prop | Type | What it does |
|---|---|---|
labelrequired | string | — |
optionsrequired | readonly ListOption[] | — |
className | string | Merged with the library classes; it does not replace them. |
defaultOpen | boolean | The list starts open. To photograph the panel on the test bench. |
defaultValue | string | null | The key chosen at the start, without controlling it from outside. |
disabled | boolean | — |
error | string | — |
excludeFromTabOrder | boolean | Out of the tab order. It can still be focused by hand (`.focus()`), which is the difference with disabling it: a disabled control cannot be focused in any way. |
hiddenLabel | boolean | The label for the screen reader only. For when the field sits inside another one that already explains itself (the prefix of a phone), never to save space: without a visible label you cannot tell what is being chosen. |
hint | string | — |
id | string | It goes to the TRIGGER, which is the focusable node and the one carrying the role of the field: it is the one that must be pointable from outside with an `aria-controls`, a step of a guided tour or a shortcut. The inner parts (the label, the list, the hint) keep their own `id`s, derived from this one when it is passed, so they do not repeat with two selectors on the same page. |
invalid | boolean | — |
loading | boolean | The options are arriving. Marks the field as busy, shows a ring and a `loadingText` row in the list; the options that were already there stay. |
loadingText | string | What the loading row says. "Loading…" by default. |
name | string | So that the value travels in a native form. |
native | boolean | A SYSTEM `<select>` instead of the own panel. On a phone the operating system's picker appears, which is what is wanted when the list is long or is filled in with a finger. The icons and the two lines per option are lost (`renderOption`, `renderValue`, `icon`, `description`); groups (`group`) come out as `<optgroup>`. Same field on the outside and same props. |
offset | number | Separation in pixels between the field and the list. None by default. |
onBlur | () => void | On leaving the field. The form uses it to mark that it has been touched. |
onChange | (key: string | null) => void | — |
onChangeEvent | (event: ChangeEvent<HTMLSelectElement>) => void | With `native`: the `<select>` event, to migrate a handler that reads `e.target.value`. `onChange` still delivers the key, and both are called. |
onOpenChange | (open: boolean) => void | — |
open | boolean | Open or closed, driven from OUTSIDE. With this the state stops living here: whoever passes it decides, and `onOpenChange` notifies of each attempt. It is needed to open the list from somewhere else (a keyboard shortcut, a step of a guided tour) without touching the trigger. |
panelWidth | "content" | "trigger" | How wide the list is. `trigger` (the default) leaves it the same as the field, which is what is expected of a normal selector. `content` lets it grow until the options fit. It is needed when the trigger is deliberately NARROWER than the options: a phone prefix shows "+34" and the list says "+34 · Spain". Measured before this existed: the panel came out 69 px wide and the 19 options appeared cut mid-word ("Spai", "Portu"). |
placeholder | string | Text when nothing is chosen. It does not replace the label. |
placement | Placement | Where the list appears. By default, attached right below the field. |
readOnly | boolean | — |
renderOption | OptionRender<ListOption> | Paints the inside of each option in the list: an avatar, two lines, a price. The name the screen reader reads is still `label`. |
renderValue | (option: SelectOption) => ReactNode | Paints what is chosen inside the closed field. Without this the icon and the label (or `shortLabel`) show. What is announced is what is painted, so it should carry the text. |
required | boolean | — |
shouldCloseOnSelect | boolean | Choosing an option does NOT close the list. By default it closes it, which is what anyone expects in a single-option dropdown; this is for whoever builds on top something that lets you keep looking. |
size | "sm" | "md" | "lg" | — |
validationBehavior | "aria" | "native" | — |
value | string | null | The chosen key, controlled from outside. |
variant | FieldVariant | — |
It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.
CSS variables
These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.
| Variable | Default |
|---|---|
--amset-select-arrow | 1rem |
How to change it
.amset-select {
--amset-select-arrow: ...;
}In the test bench
Every state and variant, to try one at a time, on its page at ui.amset.tech:
