> Leaf UI 0.6.0 · Package: @ifhover/leaf-ui · Source: b30c3d3a8fa9c5e15d0d0522cebfbd33f60c6b06
> Browser documentation: https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/select.html

> For AI agents: the complete documentation index is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms.txt, the full documentation bundle is available at https://ifhover.github.io/leaf-ui/v/0.6.0/en/llms-full.txt.

# Select

Choose one or several options from a dropdown list.

## Examples

Use options with unique nonempty string values. onChange returns the value and option; clearing returns an empty string and undefined. Arrow keys highlight options, Enter selects, Escape closes and Tab moves on.


Choose a team

Default team

Disabled team

basic.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/basic.tsx"
import { Form, FormField, Select } from '@ifhover/leaf-ui';

const options = [
  { label: 'Design studio', value: 'design' },
  { label: 'Product team', value: 'product' },
  { label: 'Engineering team', value: 'engineering' },
  { label: 'Archived project', value: 'archived', disabled: true },
];

export function SelectBasic() {
  return (
    <Form layout="vertical">
      <FormField label="Choose a team">
        <Select aria-label="Choose a team" options={options} placeholder="Select a team" />
      </FormField>
      <FormField label="Default team">
        <Select aria-label="Default team" options={options} defaultValue="design" />
      </FormField>
      <FormField label="Disabled team">
        <Select aria-label="Disabled team" options={options} disabled defaultValue="product" />
      </FormField>
    </Form>
  );
}
```

Expand code


Project visibility

Current:Not selected

Team required · Error

controlled.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/controlled.tsx"
import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';

import { useState } from 'react';

export function SelectControlled() {
  const [value, setValue] = useState('');
  return (
    <Form layout="vertical">
      <FormField label="Project visibility">
        <Select
          aria-label="Project visibility"
          value={value}
          placeholder="Select visibility"
          allowClear
          onChange={setValue}
          options={[
            { label: 'Public', value: 'public' },
            { label: 'Team', value: 'team' },
            { label: 'Private', value: 'private' },
          ]}
        />
      </FormField>
      <Text tone="muted">Current:{value || 'Not selected'}</Text>
      <FormField label="Team required · Error">
        <Select
          aria-label="Team required"
          status="error"
          placeholder="Select a team"
          options={[{ label: 'Design team', value: 'design' }]}
        />
      </FormField>
    </Form>
  );
}
```

Expand code


smSearch

mdSearch

lgSearch

control-sizes.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/guide/control-sizes.tsx"
import type { ControlSize } from '@ifhover/leaf-ui';

import { Button, Flex, Input, Select, Text } from '@ifhover/leaf-ui';

export function ControlSizes() {
  return (
    <Flex direction="column" gap="md">
      {(['sm', 'md', 'lg'] as const).map((size: ControlSize) => (
        <Flex key={size} align="center" gap="sm" wrap>
          <Text tone="muted">{size}</Text>
          <Input size={size} aria-label={`${size} search`} placeholder="Search projects" />
          <Select
            size={size}
            aria-label={`${size} status`}
            defaultValue="all"
            options={[
              { label: 'All statuses', value: 'all' },
              { label: 'Active', value: 'active' },
            ]}
          />
          <Button size={size}>Search</Button>
        </Flex>
      ))}
    </Flex>
  );
}
```

Expand code

## Search and multiple selection

showSearch filters existing options; multiple accepts an array and keeps the menu open while choosing.


Search teams

Choose several teams

Design

design

search.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/search.tsx"
import type { SelectOption } from '@ifhover/leaf-ui';

import { Form, FormField, Select, Text } from '@ifhover/leaf-ui';

import { useState } from 'react';

const options: SelectOption[] = [
  { value: 'design', label: 'Design' },
  { value: 'engineering', label: 'Engineering' },
  { value: 'marketing', label: 'Marketing' },
];

export function SelectSearch() {
  const [teams, setTeams] = useState<string[]>(['design']);
  return (
    <Form layout="vertical">
      <FormField label="Search teams">
        <Select
          aria-label="Search teams"
          showSearch
          allowClear
          options={options}
          placeholder="Type to search"
        />
      </FormField>
      <FormField label="Choose several teams">
        <Select
          aria-label="Choose several teams"
          multiple
          showSearch
          allowClear
          options={options}
          value={teams}
          onChange={setTeams}
        />
      </FormField>
      <output>
        <Text tone="muted">{teams.join(', ') || '—'}</Text>
      </output>
    </Form>
  );
}
```

Expand code

## Popup width and content


Automatic popup width

Fixed popup width

Selection limit

Leaf Studio+1Leaf Notes

Loading options

Empty options

popup.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/popup.tsx"
import type { SelectOption } from '@ifhover/leaf-ui';

import { Form, FormField, Select } from '@ifhover/leaf-ui';

const options: SelectOption[] = [
  { value: 'garden', label: 'Leaf Garden Design System' },
  { value: 'studio', label: 'Leaf Studio' },
  { value: 'notes', label: 'Leaf Notes' },
];

export function SelectPopup() {
  return (
    <Form layout="vertical">
      <FormField label="Automatic popup width">
        <Select
          aria-label="Automatic popup width"
          style={{ width: 180 }}
          options={options}
          defaultValue="studio"
        />
      </FormField>
      <FormField label="Fixed popup width">
        <Select
          aria-label="Fixed popup width"
          popupWidth={300}
          popupMaxWidth={360}
          style={{ width: 180 }}
          options={options}
          optionRender={(option) => (
            <div>
              <strong>{option.label}</strong>
              <div style={{ fontSize: 12, opacity: 0.65 }}>{option.value}</div>
            </div>
          )}
        />
      </FormField>
      <FormField label="Selection limit">
        <Select
          aria-label="Selection limit"
          multiple
          options={options}
          defaultValue={['studio', 'notes']}
          maxCount={2}
          maxTagCount={1}
          allowClear
        />
      </FormField>
      <FormField label="Loading options">
        <Select aria-label="Loading options" options={[]} loading placeholder="Loading" />
      </FormField>
      <FormField label="Empty options">
        <Select aria-label="Empty options" options={[]} notFoundContent="No projects yet" />
      </FormField>
    </Form>
  );
}
```

Expand code

## Extended features


Groups and creation5000 options

extensions.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/extensions.tsx"
import { Select, Space } from '@ifhover/leaf-ui';

const many = Array.from({ length: 5000 }, (_, i) => ({
  value: String(i),
  label: `Item ${i + 1}`,
}));

export function ExtensionDemo() {
  return (
    <Space direction="vertical" align="stretch">
      <label htmlFor="select-extension-1">
        Groups and creation
        <Select
          id="select-extension-1"
          multiple
          showSearch
          allowCreate
          options={[
            {
              label: 'Design',
              options: [
                { value: 'ui', label: 'UI design' },
                { value: 'ux', label: 'UX design' },
              ],
            },
            { label: 'Engineering', options: [{ value: 'web', label: 'Web development' }] },
          ]}
          placeholder="Select or create tags"
        />
      </label>
      <label htmlFor="select-extension-2">
        5000 options
        <Select
          id="select-extension-2"
          showSearch
          virtual
          options={many}
          placeholder="Search and select"
        />
      </label>
    </Space>
  );
}
```

Expand code

## Selected content and controlled popup


Responsive tags, constrained to the available width

DesignEngineeringMarketing+3

Controlled popup with an extra action

capabilities.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/select/capabilities.tsx"
import { Button, Flex, Select, Space, Tag, Text } from '@ifhover/leaf-ui';

import { useState } from 'react';

const options = ['Design', 'Engineering', 'Marketing', 'Operations'].map((label) => ({
  value: label,
  label,
}));

export function Capabilities() {
  const [open, setOpen] = useState(false);
  return (
    <Space direction="vertical" align="stretch">
      <Flex direction="column" gap="sm" align="center">
        <Text>
          {'Responsive tags, constrained to the available width'}
        </Text>
        <Select
          multiple
          showSearch
          defaultValue={['Design', 'Engineering', 'Marketing']}
          options={options}
          maxTagCount="responsive"
          tagRender={(option, remove) => (
            <Tag closable onClose={remove}>
              {option.label}
            </Tag>
          )}
        />
      </Flex>
      <Flex direction="column" gap="sm" align="center">
        <Text>{'Controlled popup with an extra action'}</Text>
        <Select
          options={options}
          open={open}
          onOpenChange={setOpen}
          labelRender={(option) => <strong>{option.label}</strong>}
          popupRender={(content) => (
            <>
              {content}
              <Button size="sm" variant="ghost" onClick={() => setOpen(false)}>
                {'Done'}
              </Button>
            </>
          )}
        />
      </Flex>
    </Space>
  );
}
```

Expand code

## Single-line selections

Set `tagWrap={false}` to keep selected values on one line. Long labels truncate; extra values collapse into `+N`. In very narrow controls, only the count is shown. Open the popup to review or change selections. `maxTagCount` can further limit the visible tags; `maxTagCount="responsive"` also enables automatic single-line collapsing.


Single line with long labels

A longer design team nameFrontendBackendOperations+4

Wrapping by default

A longer design team nameFrontendBackendOperations

single-line.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/select/single-line.tsx"
import { Form, FormField, Select } from '@ifhover/leaf-ui';

const options = [
  { value: '0', label: 'A longer design team name' },
  { value: '1', label: 'Frontend' },
  { value: '2', label: 'Backend' },
  { value: '3', label: 'Operations' },
];

const selected = options.map((option) => option.value);

export function SingleLine() {
  return (
    <Form layout="vertical">
      <FormField label="Single line with long labels">
        <Select
          multiple
          showSearch
          allowClear
          tagWrap={false}
          options={options}
          defaultValue={selected}
        />
      </FormField>
      <FormField label="Wrapping by default">
        <Select multiple options={options} defaultValue={selected} />
      </FormField>
    </Form>
  );
}
```

Expand code

## Select API

| Property            | Type                                                                                                                                                                 | Default               | Description                                                                                      |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------ |
| `popupPlacement`    | `'top' \| 'top-start' \| 'top-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end' \| 'right' \| 'right-start' \| 'right-end'` | —                     | Placement relative to the trigger.                                                               |
| `popupClassName`    | `string`                                                                                                                                                             | —                     | Popup styling                                                                                    |
| `popupStyle`        | `CSSProperties`                                                                                                                                                      | —                     | Popup styling                                                                                    |
| `popupRender`       | `(content: ReactNode) => ReactNode`                                                                                                                                  | —                     | Wrap or extend popup content; retain the supplied content to preserve interaction.               |
| `getPopupContainer` | `() => Element \| DocumentFragment`                                                                                                                                  | —                     | Return a popup container, including a ShadowRoot.                                                |
| `options`           | `readonly (`[`SelectOption`](#selectoption)`\|`[`SelectOptionGroup`](#selectoptiongroup-api)`)[]`                                                                    | Required              | Required. Options                                                                                |
| `size`              | `'sm' \| 'md' \| 'lg'`                                                                                                                                               | `'md'`                | Control size                                                                                     |
| `status`            | `'error' \| 'warning'`                                                                                                                                               | —                     | Validation appearance                                                                            |
| `allowClear`        | `boolean`                                                                                                                                                            | `false`               | Show a clear button                                                                              |
| `showSearch`        | `boolean`                                                                                                                                                            | `false`               | Allow typing to filter options                                                                   |
| `filterOption`      | `boolean \| ((query: string, option: `[`SelectOption`](#selectoption)`) => boolean)`                                                                                 | `true`                | Custom filtering; false supports external filtering                                              |
| `onSearch`          | `(query: string) => void`                                                                                                                                            | —                     | Search text changes                                                                              |
| `onOpenChange`      | `(open: boolean) => void`                                                                                                                                            | —                     | Option panel open-state changes                                                                  |
| `open`              | `boolean`                                                                                                                                                            | —                     | Control whether the panel is open.                                                               |
| `defaultOpen`       | `boolean`                                                                                                                                                            | —                     | Initial open state.                                                                              |
| `inputReadOnly`     | `boolean`                                                                                                                                                            | —                     | Disable typing while retaining panel selection.                                                  |
| `labelRender`       | `(option: `[`SelectOption`](#selectoption)`) => ReactNode`                                                                                                           | —                     | Customize the selected value.                                                                    |
| `tagRender`         | `(option: `[`SelectOption`](#selectoption)`, remove: () => void) => ReactNode`                                                                                       | —                     | Customize selected tags; use remove to request removal.                                          |
| `onPopupScroll`     | `(event: React.UIEvent<HTMLDivElement>) => void`                                                                                                                     | —                     | Popup scroll event, useful for loading another page.                                             |
| `popupWidth`        | `number \| string`                                                                                                                                                   | `'auto'`              | Popup width: auto fits content, trigger matches the field, or use pixels / a CSS length          |
| `popupMaxWidth`     | `number \| string`                                                                                                                                                   | `420`                 | Maximum width, also limited by the viewport                                                      |
| `loading`           | `boolean`                                                                                                                                                            | `false`               | Loading indicator                                                                                |
| `notFoundContent`   | `ReactNode`                                                                                                                                                          | —                     | Content when no options match                                                                    |
| `optionRender`      | `(option: `[`SelectOption`](#selectoption)`) => ReactNode`                                                                                                           | —                     | Custom option content                                                                            |
| `maxCount`          | `number`                                                                                                                                                             | —                     | Maximum selections; selected items remain removable                                              |
| `maxTagCount`       | `number \| 'responsive'`                                                                                                                                             | —                     | Maximum visible tags; responsive collapses them to the available width.                          |
| `tagWrap`           | `boolean`                                                                                                                                                            | `true`                | Allow selected tags to wrap; false keeps one line and collapses overflow.                        |
| `allowCreate`       | `boolean`                                                                                                                                                            | `false`               | Allow creating unmatched search text                                                             |
| `onCreate`          | `(option: `[`SelectOption`](#selectoption)`) => void`                                                                                                                | —                     | Created option callback                                                                          |
| `virtual`           | `boolean`                                                                                                                                                            | More than 100 options | Render visible items only                                                                        |
| `listHeight`        | `number`                                                                                                                                                             | `248`                 | List height                                                                                      |
| `optionHeight`      | `number`                                                                                                                                                             | `32`                  | Estimated virtual row height; follows the regional height and density. Actual rows are measured. |
| `multiple`          | `false \| true`                                                                                                                                                      | `false`               | Use arrays for multiple values; default \[]                                                      |
| `value`             | `string \| readonly string[]`                                                                                                                                        | Required              | Unique option value; empty strings are reserved                                                  |
| `defaultValue`      | `string \| readonly string[]`                                                                                                                                        | `''`                  | Controlled value / initial uncontrolled value                                                    |
| `onChange`          | `((value: string, option?: `[`SelectOption`](#selectoption)`) => void) \| ((value: string[], options: `[`SelectOption`](#selectoption)`[]) => void)`                 | —                     | Returns value and option; clearing returns an empty string and undefined                         |
| `disabled`          | `boolean`                                                                                                                                                            | `false`               | Disable interaction.                                                                             |
| `name`              | `string`                                                                                                                                                             | —                     | Field name in native FormData.                                                                   |
| `form`              | `string`                                                                                                                                                             | —                     | ID of the associated native form.                                                                |
| `required`          | `boolean`                                                                                                                                                            | `false`               | Require a non-empty value; compound controls validate once.                                      |
| `ref`               | `Ref<HTMLInputElement>`                                                                                                                                              | —                     | DOM reference                                                                                    |

### SelectOption

| Property       | Type                                              | Default               | Description                                                                                      |
| -------------- | ------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------ |
| `value`        | `string`                                          | Required              | Unique option value; empty strings are reserved                                                  |
| `searchLabel`  | `string`                                          | —                     | Search text for node labels; falls back to value                                                 |
| `label`        | `ReactNode`                                       | Required              | Option content; a string or React node                                                           |
| `disabled`     | `boolean`                                         | `false`               | Disable the option                                                                               |
| `allowCreate`  | `boolean`                                         | `false`               | Allow creating unmatched search text                                                             |
| `onCreate`     | [`(option: SelectOption) => void`](#selectoption) | —                     | Created option callback                                                                          |
| `virtual`      | `boolean`                                         | More than 100 options | Render visible items only                                                                        |
| `listHeight`   | `number`                                          | `248`                 | List height                                                                                      |
| `optionHeight` | `number`                                          | `32`                  | Estimated virtual row height; follows the regional height and density. Actual rows are measured. |

## Types

Import SelectProps to describe component props. Option types in the API table link to their detailed fields above.

```ts
import type { SelectProps, SelectOption } from '@ifhover/leaf-ui';
```

## Forms and localization

Use name to include values in FormData. Uncontrolled fields follow form reset; reset controlled values in application state. Place controls in [FormField](https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/form.md#formfield-api) for labels, help and errors. [ConfigProvider](https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/config-provider.md) supplies regional themes and translated built-in UI. Application labels and options are your responsibility.

In multiple mode, clearing returns \[]. FormData submits repeated values under the same name; read them with `formData.getAll(name)`. Empty-query Backspace removes the last enabled tag. Leaving clears the query; searches never create values. For node labels, supply searchLabel. The ref and other input attributes target the input.

The field keeps its layout width. The popup is at least as wide as the field and grows to fit its options, up to popupMaxWidth. Use `popupWidth="trigger"` for a matching width or set `popupWidth={300}` for a fixed width. Content wraps only after reaching its width limit.

## Extension types

```ts
import type { SelectOptionGroup } from '@ifhover/leaf-ui';
```

## SelectOptionGroup API

| Property  | Type                                           | Default  | Description                 |
| --------- | ---------------------------------------------- | -------- | --------------------------- |
| `label`   | `ReactNode`                                    | Required | Required. Display label     |
| `options` | `readonly `[`SelectOption`](#selectoption)`[]` | Required | Required. Available options |

## SelectOption

| Property      | Type        | Default | Description                         |
| ------------- | ----------- | ------- | ----------------------------------- |
| `value`       | `string`    | —       | Required. Controlled value.         |
| `label`       | `ReactNode` | —       | Required. Visible label or content. |
| `disabled`    | `boolean`   | —       | Disable interaction.                |
| `searchLabel` | `string`    | —       | Plain text used for searching.      |
