Skip to main content

Usage

Options shape

Each option has a value (unique), a label, and optional description, disabled, and icon.

type SelectOption = {
value: string;
label: string;
description?: string;
disabled?: boolean;
icon?: ReactNode | ((option) => ReactNode);
};

Single vs multi

The same <Select> handles both. Omit multiple (or pass false) for single-select; pass multiple for multi-select. The value / onValueChange signature changes accordingly:

ModevalueonValueChange
Singlestring(v: string) => void
Multistring[](v: string[]) => void

Commit behavior

  • Single-select commits immediately on tap and closes the sheet.
  • Multi-select uses a draft: opening the sheet copies the controlled value into a local draft. Toggling rows only mutates the draft (instant visual feedback). The blue ✓ button commits the draft to onValueChange; ✕ discards it and reverts to the controlled value. This matches native iOS multi-select sheets.

Descriptions

Pass description to render secondary text below the label.

const options = [
{ value: "AAPL", label: "AAPL", description: "Apple Inc. · NASDAQ" },
{ value: "TSLA", label: "TSLA", description: "Tesla Inc. · NASDAQ" },
];

The description is also folded into the row's aria-label ("AAPL — Apple Inc. · NASDAQ") for screen readers and agents.

Leading icons

Pass icon as a ReactNode (e.g. an <img>) or a render function that receives the option. The icon renders in a fixed-size slot to the left of the label, distinct from the trailing selected-state checkmark.

// ReactNode
const options = [
{ value: "AAPL", label: "AAPL", icon: <img src="/logos/aapl.svg" alt="" /> },
];

// render function, reused across options
const icon = (o) => <Logo symbol={o.value} />;
const options = symbols.map((s) => ({ ...s, icon }));

The search field shows automatically when multiple is set or there are more than 5 options. Force it on/off with the searchable prop.

<Select searchable options={fewOptions} value={v} onValueChange={setV} />

Typing filters by label, value, and description (case-insensitive). When the result set changes, the list scrolls back to the top.

Disabled

Disable the whole trigger with disabled, or a single option with option.disabled.

<Select value={v} onValueChange={setV} options={options} disabled />

const options = [
{ value: "a", label: "A" },
{ value: "b", label: "B", disabled: true }, // not toggleable
];

Controlled only

The component is fully controlled — you always own the value and decide what to do in onValueChange. There is no internal uncontrolled state to drift out of sync.