> Leaf UI 0.8.1 · Package: @ifhover/leaf-ui · Source: 435bab23cad7d343653d9b63b049c0b0601eeefb
> Browser documentation: https://ifhover.github.io/leaf-ui/en/components/tree-select.html

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

# TreeSelect

Choose one or more values from hierarchical data.

## Selection and search


Current value: None

basic.tsx

Copy code

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

import { useState } from 'react';

import { treeOptions } from './options';

export function TreeSelectBasic() {
  const [value, setValue] = useState<string | null>(null);
  return (
    <Space direction="vertical" align="stretch">
      <TreeSelect
        options={treeOptions}
        value={value}
        onChange={setValue}
        defaultExpandAll
        aria-label="Team"
        placeholder="Select a team"
      />
      <output>
        <Text tone="muted">Current value: {typeof value === 'string' ? value : 'None'}</Text>
      </output>
    </Space>
  );
}
```

Expand code

## Multiple selection and checks


Multiple · Select individual items

Visual design

Linked checks · Child values

Visual design

Linked checks · Parent values

Design

multiple.tsx

Copy code

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

import { treeOptions } from './options';

export function TreeSelectMultiple() {
  return (
    <Form layout="vertical">
      <FormField label="Multiple · Select individual items">
        <TreeSelect options={treeOptions} multiple defaultValue={['visual']} defaultExpandAll />
      </FormField>
      <FormField label="Linked checks · Child values">
        <TreeSelect
          options={treeOptions}
          treeCheckable
          defaultValue={['visual']}
          defaultExpandAll
        />
      </FormField>
      <FormField label="Linked checks · Parent values">
        <TreeSelect
          options={treeOptions}
          treeCheckable
          showCheckedStrategy="parent"
          defaultValue={['design']}
          defaultExpandAll
        />
      </FormField>
    </Form>
  );
}
```

Expand code

## States and panel width


Disabled

Validation feedback

Custom popup width

states.tsx

Copy code

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

import { treeOptions } from './options';

export function TreeSelectStates() {
  return (
    <Form layout="vertical">
      <FormField label="Disabled">
        <TreeSelect options={treeOptions} disabled defaultValue="web" />
      </FormField>
      <FormField label="Validation feedback">
        <TreeSelect options={treeOptions} status="error" placeholder="Select a team" />
      </FormField>
      <FormField label="Custom popup width">
        <TreeSelect options={treeOptions} popupWidth={320} popupMaxWidth={360} defaultExpandAll />
      </FormField>
    </Form>
  );
}
```

Expand code

## Extended features


extensions.tsx

Copy code

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

export function ExtensionDemo() {
  return (
    <TreeSelect
      placeholder="Expand the team to load members"
      options={[{ value: 'team', label: 'Team', isLeaf: false }]}
      virtual
      loadData={async (option) => {
        await new Promise((resolve) => setTimeout(resolve, 500));
        return [
          { value: `${option.value}-design`, label: 'Design team', isLeaf: true },
          { value: `${option.value}-dev`, label: 'Development team', isLeaf: true },
        ];
      }}
    />
  );
}
```

Expand code

## Selection limits and node rendering

The popup matches the field width by default (`popupWidth="trigger"`). Set `popupWidth="auto"` to fit the content, which may be narrower than the field, or `popupWidth={300}` for a fixed 300px width; CSS lengths are also supported. `popupMaxWidth` is unset by default. The example below fits the content with an explicit maximum width. Viewport edge avoidance still applies.


Choose up to two people; keep one tag visible

capabilities.tsx

Copy code

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

import { useState } from 'react';

const options = [
  {
    value: 'team',
    label: 'Team',
    children: [
      { value: 'alice', label: 'Alice' },
      { value: 'bob', label: 'Bob' },
      { value: 'carol', label: 'Carol' },
    ],
  },
];

export function Capabilities() {
  const [expanded, setExpanded] = useState<readonly string[]>(['team']);
  return (
    <Space direction="vertical" align="stretch">
      <Flex direction="column" gap="sm" align="center">
        <Text>
          {'Choose up to two people; keep one tag visible'}
        </Text>
        <TreeSelect
          aria-label={'Team members'}
          popupWidth="auto"
          popupMaxWidth={320}
          treeCheckable
          options={options}
          maxCount={2}
          maxTagCount={1}
          treeExpandedKeys={expanded}
          onExpand={setExpanded}
          nodeRender={(node) => <strong>{node.label}</strong>}
          filterTreeNode={(node, query) =>
            String(node.label).toLowerCase().includes(query.toLowerCase())
          }
          showSearch
        />
      </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.

Reopening preserves the expanded branches. Click a category that cannot be selected to expand it; in checkbox mode, click anywhere on a row or press Enter / Space to check it. The arrow only expands or collapses the branch. Search temporarily reveals matching branches without changing the saved expansion.


Multiple values on one line

Visual designProduct designFrontendBackend+4

single-line.tsx

Copy code

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

import { treeOptions } from './options';

export function SingleLine() {
  return (
    <Form layout="vertical">
      <FormField label="Multiple values on one line">
        <TreeSelect
          treeCheckable
          tagWrap={false}
          options={treeOptions}
          defaultValue={['visual', 'product', 'web', 'server']}
          treeDefaultExpandedKeys={['design']}
        />
      </FormField>
    </Form>
  );
}
```

Expand code

## TreeSelect 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`                                                                                                                                                             | —           | Custom class for the popup.                                                                                                                   |
| `popupStyle`              | `CSSProperties`                                                                                                                                                      | —           | Custom popup styles.                                                                                                                          |
| `popupRender`             | `(content: React.ReactNode) => React.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 `[`TreeSelectOption`](#treeselectoption)`[]`                                                                                                               | Required    | Required. Hierarchical options                                                                                                                |
| `checkStrictly`           | `boolean`                                                                                                                                                            | `false`     | Check parents and children independently                                                                                                      |
| `showCheckedStrategy`     | `'child' \| 'parent' \| 'all'`                                                                                                                                       | `'child'`   | Display and output children, consolidated parents or all checked nodes                                                                        |
| `showSearch`              | `boolean`                                                                                                                                                            | `true`      | Filter nodes by typing                                                                                                                        |
| `onSearch`                | `(query: string) => void`                                                                                                                                            | —           | Search changes                                                                                                                                |
| `allowClear`              | `boolean`                                                                                                                                                            | `true`      | Allow clearing enabled selections                                                                                                             |
| `defaultExpandAll`        | `boolean`                                                                                                                                                            | `false`     | Expand all initially                                                                                                                          |
| `treeDefaultExpandedKeys` | `readonly string[]`                                                                                                                                                  | `[]`        | Values expanded initially                                                                                                                     |
| `size`                    | `'sm' \| 'md' \| 'lg'`                                                                                                                                               | `'md'`      | Control size                                                                                                                                  |
| `status`                  | `'error' \| 'warning'`                                                                                                                                               | —           | Validation status                                                                                                                             |
| `popupWidth`              | `number \| string`                                                                                                                                                   | `'trigger'` | Popup width: trigger matches the field, auto fits content (and may be narrower), numbers specify fixed pixels; CSS lengths are also supported |
| `popupMaxWidth`           | `number \| string`                                                                                                                                                   | —           | Optional maximum width; unset by default, still bounded by the viewport                                                                       |
| `notFoundContent`         | `ReactNode`                                                                                                                                                          | No options  | Content when no options match                                                                                                                 |
| `onOpenChange`            | `(open: boolean) => void`                                                                                                                                            | —           | Open state changes                                                                                                                            |
| `loadData`                | `( option: `[`TreeSelectOption`](#treeselectoption)`, signal: AbortSignal, ) => Promise<readonly `[`TreeSelectOption`](#treeselectoption)`[]>`                       | —           | Load children on expansion; signal supports cancellation                                                                                      |
| `onLoadError`             | `(error: unknown, option: `[`TreeSelectOption`](#treeselectoption)`) => void`                                                                                        | —           | Loading failed; expand again to retry                                                                                                         |
| `virtual`                 | `boolean`                                                                                                                                                            | `false`     | Render visible items only                                                                                                                     |
| `listHeight`              | `number`                                                                                                                                                             | `256`       | List height                                                                                                                                   |
| `itemHeight`              | `number`                                                                                                                                                             | `38`        | Estimated virtual row height including spacing; follows the regional height and density. Actual rows are measured.                            |
| `inputReadOnly`           | `boolean`                                                                                                                                                            | —           | Disable typing while retaining panel selection.                                                                                               |
| `open`                    | `boolean`                                                                                                                                                            | —           | Control whether the panel is open.                                                                                                            |
| `defaultOpen`             | `boolean`                                                                                                                                                            | —           | Initial open state.                                                                                                                           |
| `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.                                                                     |
| `maxCount`                | `number`                                                                                                                                                             | —           | Maximum selection count.                                                                                                                      |
| `treeExpandedKeys`        | `readonly string[]`                                                                                                                                                  | —           | Controlled expanded nodes.                                                                                                                    |
| `onExpand`                | `(keys: string[]) => void`                                                                                                                                           | —           | Called when expanded nodes change.                                                                                                            |
| `filterTreeNode`          | `(option: `[`TreeSelectOption`](#treeselectoption)`, query: string) => boolean`                                                                                      | —           | Customize search matching for nodes.                                                                                                          |
| `nodeRender`              | `(option: `[`TreeSelectOption`](#treeselectoption)`) => ReactNode`                                                                                                   | —           | Customize node content.                                                                                                                       |
| `cacheKey`                | `string \| number`                                                                                                                                                   | —           | Changing the data version clears lazy caches and cancels stale requests.                                                                      |
| `multiple`                | `false \| true \| boolean`                                                                                                                                           | `false`     | Multiple / checkbox mode, which automatically enables multiple selection                                                                      |
| `treeCheckable`           | `false \| boolean \| true`                                                                                                                                           | `false`     | Multiple / checkbox mode, which automatically enables multiple selection                                                                      |
| `value`                   | `string \| null \| readonly string[] \| readonly string[]`                                                                                                           | `null`      | Controlled / initial value; use an array for multiple selection                                                                               |
| `defaultValue`            | `string \| null \| readonly string[] \| readonly string[]`                                                                                                           | `null`      | Controlled / initial value; use an array for multiple selection                                                                               |
| `onChange`                | `(((value: string \| null) => void) \| ((value: readonly string[]) => void)) \| ((value: readonly string[]) => void)`                                                | —           | Selection changes; clearing returns null for single mode or \[] for multiple                                                                  |
| `disabled`                | `boolean`                                                                                                                                                            | `false`     | Disable interaction.                                                                                                                          |
| `required`                | `boolean`                                                                                                                                                            | `false`     | Require a non-empty value; compound controls validate once.                                                                                   |
| `name`                    | `string`                                                                                                                                                             | —           | Field name in native FormData.                                                                                                                |
| `form`                    | `string`                                                                                                                                                             | —           | ID of the associated native form.                                                                                                             |

## TreeSelectOption

| Property          | Type                                                   | Default | Description                                    |
| ----------------- | ------------------------------------------------------ | ------- | ---------------------------------------------- |
| `value`           | `string`                                               | —       | Required. Unique value, required               |
| `label`           | `ReactNode`                                            | —       | Required. Display label, required              |
| `children`        | `readonly `[`TreeSelectOption`](#treeselectoption)`[]` | —       | Child options using the same structure         |
| `disabled`        | `boolean`                                              | —       | Disable interaction.                           |
| `selectable`      | `boolean`                                              | —       | Set false to make a category non-selectable    |
| `disableCheckbox` | `boolean`                                              | —       | Disable checks and stop propagation            |
| `icon`            | `ReactNode`                                            | —       | Node icon                                      |
| `searchLabel`     | `string`                                               | —       | Search and accessible text for non-text labels |
| `isLeaf`          | `boolean`                                              | —       | Identify a leaf node, including in lazy data.  |

## Types

Import these types to define props or prepare data.

```ts
import type { TreeSelectProps, TreeSelectOption, TreeSelectValue } from '@ifhover/leaf-ui';
```

## Usage notes

Option values must be unique and stable. Example data:

```ts lineNumbers file="../../../src/examples/en/tree-select/options.ts"
import type { TreeSelectOption } from '@ifhover/leaf-ui';
export const treeOptions: readonly TreeSelectOption[] = [
  {
    value: 'design',
    label: 'Design',
    selectable: false,
    children: [
      { value: 'visual', label: 'Visual design' },
      { value: 'product', label: 'Product design' },
      { value: 'archive', label: 'Archived team', disabled: true },
    ],
  },
  {
    value: 'engineering',
    label: 'Engineering',
    selectable: false,
    children: [
      { value: 'web', label: 'Frontend' },
      { value: 'server', label: 'Backend' },
    ],
  },
];

```

`TreeSelectValue` is `string | readonly string[] | null` and can be used for controlled state. Checkbox mode submits checked leaves by default; `showCheckedStrategy` controls both display and callback values. Disabled existing selections are preserved by clear and linked checks.

ArrowDown enters the tree, Enter selects, Space checks and Escape closes. With an empty search in multiple mode, Backspace removes the last enabled selection. FormData submits repeated values under one field name; use `getAll(name)` to read them. Uncontrolled fields respond to form reset.
