Select
A flexible single- and multi-value selection control with search, grouping, icons, images, and accessible keyboard interaction.
Select lets a user choose one or more values from a list. The component supports searchable menus, grouped options, descriptions, icons, images, clearable values, loading states, and controlled or uncontrolled usage.
The interactive examples live in the component showcase. Use “Open interactive example” above to try every mode.
Basic usage
import { Select } from '@mosmox/ui';
const options = [
{ value: 'design', label: 'Design' },
{ value: 'engineering', label: 'Engineering' },
{ value: 'support', label: 'Support' },
];
<Select label="Team" placeholder="Choose a team" options={options} />Controlled and uncontrolled values
Use defaultValue for an uncontrolled field. Use value and onChange when the parent owns the state.
const [team, setTeam] = useState('design');
<Select
label="Team"
options={options}
value={team}
onChange={(value, option) => {
setTeam(String(value));
console.log(option);
}}
/>The value is a string in single mode and a string[] in multiple mode. When an option is selected, onChange receives both the next value and the matching option (or options). Values that no longer exist in options remain in the value but are not rendered as selected labels.
Option shape
options is an array of SelectOption objects. label and description accept React nodes, so an option can contain formatted text or another small inline element.
| Property | Type | Description |
|---|---|---|
value | string | Stable value returned from onChange. |
label | ReactNode | Primary visible option content. |
description | ReactNode | Secondary text and searchable content. |
disabled | boolean | Prevents selecting the option. |
group | string | Heading shown when the group changes. |
icon | ReactNode | Inline icon shown before the label. |
image | string | Image URL shown before the label; takes precedence over icon. |
Option order is preserved. Group headings are rendered from the order of the options, so place options from the same group together.
Multiple selection
Set multiple to return an array of values. maxSelected prevents selecting more items after the limit is reached.
const [members, setMembers] = useState<string[]>([]);
<Select
label="Members"
multiple
searchable
options={memberOptions}
value={members}
onChange={(value) => setMembers(Array.isArray(value) ? value.map(String) : [])}
maxSelected={3}
/>Rich options
Each option can include a short description, an icon, an image, a group name, or a disabled state.
const options = [
{
value: 'maya',
label: 'Maya Chen',
description: 'Product designer',
image: '/avatars/maya.png',
},
{
value: 'engineering',
label: 'Engineering',
icon: <Settings size={16} />,
group: 'Departments',
},
{ value: 'archived', label: 'Archived', disabled: true },
];Options with the same group value are displayed under a group heading. icon accepts any React node, while image accepts an image URL.
Search, clear, and loading
<Select
label="Country"
searchable
clearable
loading={isLoading}
searchPlaceholder="Search countries"
options={countries}
placeholder="Select a country"
/>Search compares the entered query with the option label and description, case-insensitively. clearable adds an inline clear action when a value is selected. loading shows a spinner and prevents opening, selection, and clearing while the asynchronous operation is in progress. An empty filtered result displays “No options found”.
Menu state and custom values
The menu can be controlled independently from the selected value with open and onOpenChange:
const [open, setOpen] = useState(false);
<Select
label="Project"
open={open}
onOpenChange={setOpen}
options={projects}
placeholder="Choose a project"
/>Use renderValue to replace the default selected label or multiple-selection chips. It receives the resolved selected options and the current multiple mode:
<Select
label="Priority"
defaultValue="high"
options={priorities}
renderValue={(selected) => selected[0] ? `Priority: ${selected[0].label}` : 'Choose priority'}
/>In multiple mode, selected values render as removable chips by default. Selecting an already selected option toggles it off. maxSelected limits additions but does not remove existing values.
Appearance and states
The size values are default, sm, and xs. The variant values are outline and filled.
<Select label="Default" size="default" variant="outline" options={options} />
<Select label="Compact" size="sm" options={options} />
<Select label="Extra compact" size="xs" variant="filled" options={options} />
<Select label="Invalid" error="Choose a valid value." options={options} />
<Select label="Unavailable" disabled options={options} />
<Select label="Display only" readOnly defaultValue="design" options={options} />fullWidth makes the field fill its parent. description provides supporting text. name adds a hidden form value for native form submission.
The hidden form value is the selected string in single mode and a comma-separated list in multiple mode. id sets the trigger and generated listbox ID; if omitted, a stable ID is generated automatically. className is applied to the outer field wrapper.
Keyboard and accessibility
ArrowDownandArrowUpmove through options.HomeandEndjump to the first and last option.EnterorSpaceopens the menu and selects the active option.Escapecloses the menu.- Searchable selects focus the search field when opened.
The trigger is a button with aria-haspopup="listbox" and aria-expanded. The popup uses a listbox and selected options expose aria-selected; multiple mode adds aria-multiselectable. A visible label is associated with the trigger, while aria-label can provide an accessible name when no visible label is present. description and error are connected through aria-describedby, and an error sets aria-invalid.
Clicking outside closes the menu. Closing clears the current search query and returns focus to the trigger. disabled, readOnly, and loading all prevent opening; readOnly keeps the current value visible without allowing changes.
API Reference
| Prop | Type | Description |
|---|---|---|
options | SelectOption[] | Values rendered in the menu. |
value / defaultValue | string | string[] | Controlled or initial value. |
placeholder | string | Text shown when no value is selected. |
multiple | boolean | Enables multi-value selection. |
searchable | boolean | Adds a filter input in the menu. |
searchPlaceholder | string | Placeholder and accessible label for the search input. |
clearable | boolean | Allows clearing the current value. |
maxSelected | number | Maximum values in multiple mode. |
loading | boolean | Shows a loading state. |
size | 'default' | 'sm' | 'xs' | Control size. |
variant | 'outline' | 'filled' | Visual treatment. |
disabled / readOnly | boolean | Prevents interaction or selection changes. |
label | ReactNode | Visible field label. |
description / error | string | Supporting or validation text. |
fullWidth | boolean | Makes the field fill its parent width. |
id | string | Custom trigger and listbox ID. |
className | string | Class for the outer field wrapper. |
name | string | Native form field name. |
onChange | (value, option) => void | Called after selection changes. |
onOpenChange | (open) => void | Called when the menu opens or closes. |
open | boolean | Controlled menu visibility. |
renderValue | (selected, multiple) => ReactNode | Custom trigger value renderer. |
aria-label / aria-describedby | string | Native accessible naming and description hooks. |
Theme tokens
Select uses the shared Config Provider theme tokens for text, borders, surfaces, selected-option states, focus, spacing, radius, and typography. Component-only layout rules live in Select.css; changing the global tokens updates Select together with the other form controls. The --mm-color-control-selected token controls active and selected menu options and defaults to the Button.Light surface.