Form
Select
Single-value select following the ARIA combobox pattern: a button trigger opens a portal listbox with typeahead, keyboard navigation and an optional clear button.
Import
import { Select } from "@zephora/react";Examples
Basic
<Select
aria-label="Framework"
placeholder="Pick a framework…"
options={[
{ label: "React", value: "react" },
{ label: "Vue", value: "vue" },
{ label: "Svelte", value: "svelte" },
{ label: "Angular", value: "angular", disabled: true },
]}
/>Controlled with clear
`clearable` shows a clear button when a value is selected.
Value: utc
const [tz, setTz] = React.useState("utc");
<Select
aria-label="Timezone"
value={tz}
onValueChange={setTz}
clearable
options={[
{ label: "UTC", value: "utc" },
{ label: "Europe/Istanbul", value: "ist" },
{ label: "America/New_York", value: "nyc" },
]}
/>
<p>Value: {tz || "(none)"}</p>Sizes and invalid
<Select size="sm" aria-label="Small" options={options} />
<Select size="lg" invalid aria-label="Large invalid" options={options} />Grouped with descriptions
Options sharing a `group` render under a non-interactive header; `description` adds secondary text under the label.
<Select
aria-label="Database"
placeholder="Pick a database…"
options={[
{ label: "PostgreSQL", value: "pg", group: "Relational", description: "Battle-tested SQL" },
{ label: "MySQL", value: "mysql", group: "Relational" },
{ label: "MongoDB", value: "mongo", group: "Document", description: "JSON documents" },
{ label: "Redis", value: "redis", group: "Key-value" },
]}
/>API
Select props
| Prop | Type | Default | Description |
|---|---|---|---|
options * | SelectOption[] | — | Options shown in the listbox. Plain { label, value } arrays keep working — see the SelectOption table for the extended fields. |
value | string | — | Controlled selected value ("" = nothing selected). |
defaultValue | string | — | Initial value in uncontrolled mode. |
onValueChange | (value: string) => void | — | Called with the newly selected value. |
placeholder | string | "Select…" | Text shown when nothing is selected. |
size | "sm" | "md" | "lg" | "md" | Trigger scale. |
invalid | boolean | false | Marks the trigger invalid (aria-invalid). |
disabled | boolean | false | Disables the trigger. |
clearable | boolean | false | Shows a clear button when a value is selected. |
loading | boolean | false | Shows a spinner row in the popup and sets aria-busy on the listbox. |
renderOption | (option: SelectOption, state: { selected, active }) => ReactNode | — | Custom option renderer — replaces the default icon/label/description layout. |
renderValue | (selected: SelectOption) => ReactNode | — | Custom trigger value renderer for the selected option. |
aria-label / aria-labelledby | string | — | Accessible name for the trigger and listbox. |
unstyled | boolean | false | Headless mode — drops Zephora classes so your own CSS can style it. |
…rest | HTMLAttributes<HTMLDivElement> | — | Forwarded to the root wrapper element. |
SelectOption (shared by Select, Combobox, MultiSelect) props
| Prop | Type | Default | Description |
|---|---|---|---|
label * | ReactNode | — | Option content. Provide textValue when this is not a plain string. |
value * | string | — | Unique option value. |
disabled | boolean | — | Disables the option. |
icon | ReactNode | — | Leading visual rendered before the label. |
description | ReactNode | — | Secondary text rendered under the label. |
group | string | — | Options sharing a group render under a non-interactive group header. |
textValue | string | — | Plain-text used for filtering/typeahead when label is not a string. |
Keyboard
| Key | Action |
|---|---|
Enter / Space / Arrow Down / Arrow Up | Opens the listbox from the trigger. |
Arrow Down / Arrow Up | Moves the active option (skips disabled). |
Home / End | Moves to the first / last enabled option. |
Enter / Space | Selects the active option and closes. |
Escape | Closes the listbox without selecting. |
A-Z | Typeahead — jumps to the next option starting with the typed text. |