Mosmox UI
Current version
GitHubnpmjs.com
Forms & Inputs

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.

PropertyTypeDescription
valuestringStable value returned from onChange.
labelReactNodePrimary visible option content.
descriptionReactNodeSecondary text and searchable content.
disabledbooleanPrevents selecting the option.
groupstringHeading shown when the group changes.
iconReactNodeInline icon shown before the label.
imagestringImage 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”.

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

  • ArrowDown and ArrowUp move through options.
  • Home and End jump to the first and last option.
  • Enter or Space opens the menu and selects the active option.
  • Escape closes 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

PropTypeDescription
optionsSelectOption[]Values rendered in the menu.
value / defaultValuestring | string[]Controlled or initial value.
placeholderstringText shown when no value is selected.
multiplebooleanEnables multi-value selection.
searchablebooleanAdds a filter input in the menu.
searchPlaceholderstringPlaceholder and accessible label for the search input.
clearablebooleanAllows clearing the current value.
maxSelectednumberMaximum values in multiple mode.
loadingbooleanShows a loading state.
size'default' | 'sm' | 'xs'Control size.
variant'outline' | 'filled'Visual treatment.
disabled / readOnlybooleanPrevents interaction or selection changes.
labelReactNodeVisible field label.
description / errorstringSupporting or validation text.
fullWidthbooleanMakes the field fill its parent width.
idstringCustom trigger and listbox ID.
classNamestringClass for the outer field wrapper.
namestringNative form field name.
onChange(value, option) => voidCalled after selection changes.
onOpenChange(open) => voidCalled when the menu opens or closes.
openbooleanControlled menu visibility.
renderValue(selected, multiple) => ReactNodeCustom trigger value renderer.
aria-label / aria-describedbystringNative 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.