Skip to content

Select

Accessible select based on react-aria-components (custom listbox, not a native <select>). Token-driven; invalid state flows from the RAC form context via group-data-[invalid]. label/description render inside so RAC links the accessible name — this component is the field, which is why there is no separate SelectField to compose those around it. Compose options with SelectItem.

import { Select } from "@evolve-storefront/ui/select";
Design notes

The error message is rendered here rather than composed, for the same reason: children belongs to the options, so a caller has nowhere to put a FieldError the way a TextField caller does. RAC renders nothing when the field is valid.

Preview

DefaultOpen in Storybook ↗

Usage

<Select name="country" label={t("country")} placeholder={t("pickCountry")} onValueChange={setCountry}>
{countries.map((country) => (
<SelectItem key={country.code} id={country.code}>
{country.name}
</SelectItem>
))}
</Select>;

Props

PropTypeDescription
data-component?undefined

Not a caller's to rename — see controlProps.

name?string
autoComplete?string

The autofill hint for the hidden native select, such as country.

value?string
defaultValue?string
onValueChange?((value: string) => void)

Hands over the chosen value. Not onChange (that hands over an event) and not RAC's onSelectionChange (that hands over a Key, which is the primitive's vocabulary, not ours).

disabled?boolean
required?boolean
invalid?boolean
children?ReactNode
label?ReactNode

RAC-linked visible label. Rendered inside the select so RAC wires the accessible name.

description?ReactNode
labelClassName?string
placeholder?string
placement?SelectPlacement

Any other props go to the root element, like on a plain HTML element.

Change the layout

Pass children to Select to arrange its parts yourself. Every part is exported from @evolve-storefront/ui/select.

SelectItemA single option inside Select.
PropTypeDescription
idstring

The value reported by onValueChange when this option is chosen.

disabled?boolean
textValue?string

Announced instead of the children when the option is not plain text.

Any other props go to the root element, like on a plain HTML element.

Examples

VariantsThe states side by side: a chosen value, invalid, and disabled.
<Select name="country" label={t("country")} defaultValue={address.country}>{options}</Select>
// Marks the field without a reason; prefer validationErrors on the Form.
<Select name="country" label={t("country")} invalid={!country}>{options}</Select>
<Select name="country" label={t("country")} defaultValue="nl" disabled>{options}</Select>
Open in Storybook ↗
With ErrorThe real path: a Form supplies validationErrors keyed by field name, and RAC routes the entry to the select's own FieldError.
// The Form routes the error to the select by its name.
<Form validationErrors={validationErrors}>
<Select name="country" label={t("country")} required>
{options}
</Select>
</Form>;
Open in Storybook ↗