> Leaf UI 0.8.1 · Package: @ifhover/leaf-ui · Source: 435bab23cad7d343653d9b63b049c0b0601eeefb
> Browser documentation: https://ifhover.github.io/leaf-ui/en/components/tree.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.

# Tree

Browse, select and check hierarchical content.

## Browse and select


 

Design resources

 

Component guidelines

 

Brand colors

 

Archived resources (disabled)

 

Engineering

Selected: None

basic.tsx

Copy code

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

import { useState } from 'react';

import { treeData } from './data';

export function TreeBasic() {
  const [selected, setSelected] = useState<readonly string[]>([]);
  return (
    <Space direction="vertical" align="stretch">
      <Tree
        data={treeData}
        defaultExpandedKeys={['design']}
        showIcon
        selectedKeys={selected}
        onSelect={setSelected}
      />
      <output>
        <Text tone="muted">Selected: {selected.join(', ') || 'None'}</Text>
      </output>
    </Space>
  );
}
```

Expand code

## Linked checks


 

Design resources

 

Component guidelines

 

Brand colors

 

Archived resources (disabled)

 

Engineering

 

Website

 

Mobile app

Checked keys: components

checkable.tsx

Copy code

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

import { useState } from 'react';

import { treeData } from './data';

export function TreeCheckable() {
  const [checked, setChecked] = useState<readonly string[]>(['components']);
  return (
    <Space direction="vertical" align="stretch">
      <Tree
        data={treeData}
        checkable
        selectable={false}
        defaultExpandAll
        showLine
        checkedKeys={checked}
        onCheck={setChecked}
      />
      <output>
        <Text tone="muted">Checked keys: {checked.join(', ') || 'None'}</Text>
      </output>
    </Space>
  );
}
```

Expand code

## Independent checks

Parents and children are checked independently.


 

Design resources

 

Component guidelines

 

Brand colors

 

Archived resources (disabled)

 

Engineering

 

Website

 

Mobile app

strict.tsx

Copy code

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

import { treeData } from './data';

export function TreeStrict() {
  return <Tree data={treeData} checkable checkStrictly selectable={false} defaultExpandAll />;
}
```

Expand code

## Search nodes


 

Design resources

 

Engineering

search.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/en/tree/search.tsx"
import { Input, Space, Tree } from '@ifhover/leaf-ui';

import { Search } from 'lucide-react';

import { useState } from 'react';

import { treeData } from './data';

export function TreeSearch() {
  const [query, setQuery] = useState('');
  return (
    <Space direction="vertical" align="stretch">
      <Input
        aria-label="Search nodes"
        placeholder="Search nodes"
        prefix={<Search />}
        value={query}
        onChange={(event) => setQuery(event.target.value)}
        allowClear
      />
      <Tree data={treeData} searchValue={query} showIcon />
    </Space>
  );
}
```

Expand code

## Extended features

Drag a handle to the upper, middle or lower part of a target row to place a node before, inside or after it. With the keyboard, Space picks up or drops, Up/Down chooses a target, Left/Right places before/after it, and Escape cancels. Update `data` with the new tree returned by `onDrop`.


Expand Projects to load children. Move nodes using their handles.

 

Projects

 

Archive

10000 nodes, only visible rows are mounted.

 

Node 1

 

Node 2

 

Node 3

 

Node 4

 

Node 5

 

Node 6

 

Node 7

 

Node 8

 

Node 9

 

Node 10

 

Node 11

extensions.tsx

Copy code

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

import { Space, Tree } from '@ifhover/leaf-ui';

import { useState } from 'react';

const initial: TreeNode[] = [
  { key: 'projects', title: 'Projects', isLeaf: false },
  { key: 'archive', title: 'Archive', children: [{ key: 'draft', title: 'Draft', isLeaf: true }] },
];

export function ExtensionDemo() {
  const [data, setData] = useState(initial);
  return (
    <Space direction="vertical" align="stretch">
      <p>Expand Projects to load children. Move nodes using their handles.</p>
      <Tree
        data={data}
        loadData={async (node, signal) => {
          await new Promise<void>((resolve, reject) => {
            const timer = setTimeout(resolve, 500);
            signal.addEventListener(
              'abort',
              () => {
                clearTimeout(timer);
                reject(new DOMException('Aborted', 'AbortError'));
              },
              { once: true },
            );
          });
          return [
            { key: `${node.key}-design`, title: 'Design', isLeaf: true },
            { key: `${node.key}-dev`, title: 'Development', isLeaf: true },
          ];
        }}
        draggable
        onDrop={(info) => setData(info.data)}
      />
      <p>10000 nodes, only visible rows are mounted.</p>
      <Tree
        virtual
        height={240}
        data={Array.from({ length: 10000 }, (_, i) => ({
          key: String(i),
          title: `Node ${i + 1}`,
        }))}
      />
    </Space>
  );
}
```

Expand code

## Refreshing asynchronous data

Update `cacheKey` to refresh lazy caches for the same node keys. Changing `data` or `loadData` also clears caches; keep their references stable across unrelated renders. Cancelled requests cannot overwrite the new data.


Refresh data

 

Documents

The root key stays stable. A new cache version discards old children and cancels pending requests.

capabilities.tsx

Copy code

```tsx lineNumbers file="../../../src/examples/tree/capabilities.tsx"
import { Button, Space, Text, Tree, type TreeNode } from '@ifhover/leaf-ui';

import { useCallback, useMemo, useState } from 'react';

export function TreeCacheRefresh() {
  const [version, setVersion] = useState(1);
  const data = useMemo<readonly TreeNode[]>(
    () => [{ key: 'root', title: 'Documents', isLeaf: false }],
    [true],
  );
  const loadData = useCallback(
    async (node: TreeNode, signal: AbortSignal): Promise<readonly TreeNode[]> => {
      await new Promise<void>((resolve) => {
        const timer = setTimeout(resolve, 400);
        signal.addEventListener(
          'abort',
          () => {
            clearTimeout(timer);
            resolve();
          },
          { once: true },
        );
      });
      if (signal.aborted) return [];
      return [
        {
          key: `${node.key}-${version}`,
          title: `${'Release'} ${version}`,
          isLeaf: true,
        },
      ];
    },
    [true, version],
  );
  return (
    <Space direction="vertical" align="stretch">
      <div>
        <Button variant="outline" onClick={() => setVersion((value) => value + 1)}>
          {'Refresh data'}
        </Button>
      </div>
      <Tree
        aria-label={'Versioned documents'}
        data={data}
        loadData={loadData}
        cacheKey={version}
        defaultExpandedKeys={['root']}
      />
      <Text tone="muted">
        {'The root key stays stable. A new cache version discards old children and cancels pending requests.'}
      </Text>
    </Space>
  );
}
```

Expand code

## Tree API

| Property              | Type                                                                                                      | Default    | Description                                                                                                        |
| --------------------- | --------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `data`                | `readonly `[`TreeNode`](#treenode)`[]`                                                                    | Required   | Required. Tree nodes                                                                                               |
| `selectedKeys`        | `readonly string[]`                                                                                       | `[]`       | Controlled / initial selected keys                                                                                 |
| `defaultSelectedKeys` | `readonly string[]`                                                                                       | `[]`       | Controlled / initial selected keys                                                                                 |
| `onSelect`            | `(keys: string[], info: `[`TreeSelectInfo`](#treeselectinfo)`) => void`                                   | —          | Selection changes                                                                                                  |
| `multiple`            | `boolean`                                                                                                 | `false`    | Multiple selection / allow selection                                                                               |
| `selectable`          | `boolean`                                                                                                 | `true`     | Multiple selection / allow selection                                                                               |
| `checkable`           | `boolean`                                                                                                 | `false`    | Show checkboxes / disable linked checks                                                                            |
| `checkStrictly`       | `boolean`                                                                                                 | `false`    | Show checkboxes / disable linked checks                                                                            |
| `checkedKeys`         | `readonly string[]`                                                                                       | `[]`       | Controlled / initial checked keys                                                                                  |
| `defaultCheckedKeys`  | `readonly string[]`                                                                                       | `[]`       | Controlled / initial checked keys                                                                                  |
| `onCheck`             | `(keys: string[], info: `[`TreeCheckInfo`](#treecheckinfo)`) => void`                                     | —          | Check changes, including linked results                                                                            |
| `expandedKeys`        | `readonly string[]`                                                                                       | `[]`       | Controlled / initial expanded keys                                                                                 |
| `defaultExpandedKeys` | `readonly string[]`                                                                                       | `[]`       | Controlled / initial expanded keys                                                                                 |
| `defaultExpandAll`    | `boolean`                                                                                                 | `false`    | Expand all nodes initially                                                                                         |
| `onExpand`            | `(keys: string[], info: `[`TreeExpandInfo`](#treeexpandinfo)`) => void`                                   | —          | Expansion changes                                                                                                  |
| `disabled`            | `boolean`                                                                                                 | `false`    | Disable interaction.                                                                                               |
| `showLine`            | `boolean`                                                                                                 | `false`    | Disable interaction / show icons / show connecting lines                                                           |
| `showIcon`            | `boolean`                                                                                                 | `false`    | Disable interaction / show icons / show connecting lines                                                           |
| `searchValue`         | `string`                                                                                                  | `''`       | Filter nodes, retaining matches and ancestors                                                                      |
| `filterTreeNode`      | `(node: `[`TreeNode`](#treenode)`, query: string) => boolean`                                             | —          | Custom search predicate                                                                                            |
| `notFoundContent`     | `ReactNode`                                                                                               | No options | Content when no nodes match                                                                                        |
| `loadData`            | `(node: `[`TreeNode`](#treenode)`, signal: AbortSignal) => Promise<readonly `[`TreeNode`](#treenode)`[]>` | —          | Load children on expansion; signal supports cancellation                                                           |
| `cacheKey`            | `string \| number`                                                                                        | —          | Changing the data version clears lazy caches and cancels stale requests.                                           |
| `nodeRender`          | `(node: `[`TreeNode`](#treenode)`) => ReactNode`                                                          | —          | Customize node content.                                                                                            |
| `onLoad`              | `(node: `[`TreeNode`](#treenode)`, children: readonly `[`TreeNode`](#treenode)`[]) => void`               | —          | Children loaded                                                                                                    |
| `onLoadError`         | `(error: unknown, node: `[`TreeNode`](#treenode)`) => void`                                               | —          | Loading failed; expand again to retry                                                                              |
| `virtual`             | `boolean`                                                                                                 | `false`    | Render visible nodes only; enable for large trees.                                                                 |
| `height`              | `number`                                                                                                  | `320`      | Height, numbers are in pixels                                                                                      |
| `itemHeight`          | `number`                                                                                                  | `42`       | Estimated virtual row height including spacing; follows the regional height and density. Actual rows are measured. |
| `draggable`           | `boolean \| ((node: `[`TreeNode`](#treenode)`) => boolean)`                                               | `false`    | Enable node drag handles                                                                                           |
| `allowDrop`           | `(info: Omit<`[`TreeDropInfo`](#treedropinfo-api)`, 'data'>) => boolean`                                  | —          | Restrict drop positions                                                                                            |
| `onDrop`              | `(info: `[`TreeDropInfo`](#treedropinfo-api)`) => void`                                                   | —          | Drop result; update data in the caller                                                                             |

## TreeNode

| Property          | Type                                   | Default | Description                                              |
| ----------------- | -------------------------------------- | ------- | -------------------------------------------------------- |
| `key`             | `string`                               | —       | Required. Unique key, required                           |
| `title`           | `ReactNode`                            | —       | Required. Node content, required                         |
| `children`        | `readonly `[`TreeNode`](#treenode)`[]` | —       | Child nodes using this same structure                    |
| `icon`            | `ReactNode`                            | —       | Custom icon                                              |
| `disabled`        | `boolean`                              | —       | Disable interaction.                                     |
| `selectable`      | `boolean`                              | —       | Allow selection / display a checkbox; enabled by default |
| `checkable`       | `boolean`                              | —       | Allow selection / display a checkbox; enabled by default |
| `disableCheckbox` | `boolean`                              | —       | Disable checks and stop check propagation                |
| `searchLabel`     | `string`                               | —       | Search and accessible text for non-text titles           |
| `isLeaf`          | `boolean`                              | —       | false allows asynchronously loaded children              |

## TreeSelectInfo

| Property        | Type                                   | Default | Description                                |
| --------------- | -------------------------------------- | ------- | ------------------------------------------ |
| `node`          | [`TreeNode`](#treenode)                | —       | Required. Operated node                    |
| `selected`      | `boolean`                              | —       | Required. Whether the node is now selected |
| `selectedNodes` | `readonly `[`TreeNode`](#treenode)`[]` | —       | Required. All selected nodes               |

## TreeCheckInfo

| Property          | Type                                   | Default | Description                               |
| ----------------- | -------------------------------------- | ------- | ----------------------------------------- |
| `node`            | [`TreeNode`](#treenode)                | —       | Required. Operated node                   |
| `checked`         | `boolean`                              | —       | Required. Whether the node is now checked |
| `checkedNodes`    | `readonly `[`TreeNode`](#treenode)`[]` | —       | Required. All fully checked nodes         |
| `halfCheckedKeys` | `readonly string[]`                    | —       | Required. Partially checked keys          |

## TreeExpandInfo

| Property   | Type                    | Default | Description                                |
| ---------- | ----------------------- | ------- | ------------------------------------------ |
| `node`     | [`TreeNode`](#treenode) | —       | Required. Operated node                    |
| `expanded` | `boolean`               | —       | Required. Whether the node is now expanded |

## Types

Import these types to define props or prepare data.

```ts
import type { TreeProps, TreeNode, TreeSelectInfo, TreeCheckInfo, TreeExpandInfo } from '@ifhover/leaf-ui';
```

## Usage notes

Each node needs a unique, stable `key`. Example data:

```ts lineNumbers file="../../../src/examples/en/tree/data.ts"
import type { TreeNode } from '@ifhover/leaf-ui';
export const treeData: readonly TreeNode[] = [
  {
    key: 'design',
    title: 'Design resources',
    children: [
      { key: 'components', title: 'Component guidelines' },
      { key: 'colors', title: 'Brand colors' },
      { key: 'archive', title: 'Archived resources (disabled)', disabled: true },
    ],
  },
  {
    key: 'engineering',
    title: 'Engineering',
    children: [
      { key: 'web', title: 'Website' },
      { key: 'mobile', title: 'Mobile app' },
    ],
  },
];

```

Checking a parent checks enabled children; disabled nodes are unaffected. Partially checked children create a mixed parent. `checkStrictly` keeps checks independent. In multiple mode, clicking toggles selection; Shift selects a range of visible nodes.

Use up/down to move, right/left to expand/collapse, Home/End to jump, Enter to select and Space to check. Typing locates nodes. Search temporarily reveals matching paths; clearing it restores the original expansion.

## Extension types

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

## TreeDropInfo API

| Property   | Type                              | Default  | Description                                              |
| ---------- | --------------------------------- | -------- | -------------------------------------------------------- |
| `node`     | [`TreeNode`](#treenode)           | Required | Required. Operated node                                  |
| `target`   | [`TreeNode`](#treenode)           | Required | Required. Target node receiving the drop.                |
| `position` | `'before' \| 'inside' \| 'after'` | Required | Required. Place before, inside or after the target node. |
| `data`     | [`TreeNode`](#treenode)`[]`       | Required | Required. Hierarchical data                              |

## moveTreeNode

Move a node in a custom data operation. This pure function returns a new tree without changing its input. It returns `null` for missing targets, self moves or moves into descendants. Tree already includes the new data in its `onDrop` callback.

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

const next = moveTreeNode(data, 'design', 'team', 'inside');
```

| Parameter    | Type                               | Description                           |
| ------------ | ---------------------------------- | ------------------------------------- |
| `data`       | [`readonly TreeNode[]`](#treenode) | Source tree                           |
| `key`        | `string`                           | Node key to move                      |
| `target`     | `string`                           | Target node key                       |
| `position`   | `'before' \| 'inside' \| 'after'`  | Position relative to the target       |
| Return value | [`TreeNode[] \| null`](#treenode)  | New tree, or null for an invalid move |
