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, and this page is available as Markdown at https://ifhover.github.io/leaf-ui/v/0.6.0/en/components/tree.md.
  • English
  • Tree

    Browse, select and check hierarchical content.

    Browse and select

    Design resources
    Component guidelines
    Brand colors
    Archived resources (disabled)
    Selected: None
    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>
      );
    }
    

    Linked checks

    Design resources
    Component guidelines
    Brand colors
    Archived resources (disabled)
    Engineering
    Website
    Mobile app
    Checked keys: components
    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>
      );
    }
    

    Independent checks

    Parents and children are checked independently.

    Design resources
    Component guidelines
    Brand colors
    Archived resources (disabled)
    Engineering
    Website
    Mobile app
    strict.tsx
    import { Tree } from '@ifhover/leaf-ui';
    
    import { treeData } from './data';
    
    export function TreeStrict() {
      return <Tree data={treeData} checkable checkStrictly selectable={false} defaultExpandAll />;
    }
    

    Search nodes

    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>
      );
    }
    

    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.

    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
    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>
      );
    }
    

    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.

    Documents
    The root key stays stable. A new cache version discards old children and cancels pending requests.
    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>
      );
    }
    

    Tree API

    PropertyTypeDefaultDescription
    datareadonly TreeNode[]RequiredRequired. Tree nodes
    selectedKeysreadonly string[][]Controlled / initial selected keys
    defaultSelectedKeysreadonly string[][]Controlled / initial selected keys
    onSelect(keys: string[], info: TreeSelectInfo) => void—Selection changes
    multiplebooleanfalseMultiple selection / allow selection
    selectablebooleantrueMultiple selection / allow selection
    checkablebooleanfalseShow checkboxes / disable linked checks
    checkStrictlybooleanfalseShow checkboxes / disable linked checks
    checkedKeysreadonly string[][]Controlled / initial checked keys
    defaultCheckedKeysreadonly string[][]Controlled / initial checked keys
    onCheck(keys: string[], info: TreeCheckInfo) => void—Check changes, including linked results
    expandedKeysreadonly string[][]Controlled / initial expanded keys
    defaultExpandedKeysreadonly string[][]Controlled / initial expanded keys
    defaultExpandAllbooleanfalseExpand all nodes initially
    onExpand(keys: string[], info: TreeExpandInfo) => void—Expansion changes
    disabledbooleanfalseDisable interaction.
    showLinebooleanfalseDisable interaction / show icons / show connecting lines
    showIconbooleanfalseDisable interaction / show icons / show connecting lines
    searchValuestring''Filter nodes, retaining matches and ancestors
    filterTreeNode(node: TreeNode, query: string) => boolean—Custom search predicate
    notFoundContentReactNodeNo optionsContent when no nodes match
    loadData(node: TreeNode, signal: AbortSignal) => Promise<readonly TreeNode[]>—Load children on expansion; signal supports cancellation
    cacheKeystring | number—Changing the data version clears lazy caches and cancels stale requests.
    nodeRender(node: TreeNode) => ReactNode—Customize node content.
    onLoad(node: TreeNode, children: readonly TreeNode[]) => void—Children loaded
    onLoadError(error: unknown, node: TreeNode) => void—Loading failed; expand again to retry
    virtualbooleanfalseRender visible nodes only; enable for large trees.
    heightnumber320Height, numbers are in pixels
    itemHeightnumber42Estimated virtual row height including spacing; follows the regional height and density. Actual rows are measured.
    draggableboolean | ((node: TreeNode) => boolean)falseEnable node drag handles
    allowDrop(info: Omit<TreeDropInfo, 'data'>) => boolean—Restrict drop positions
    onDrop(info: TreeDropInfo) => void—Drop result; update data in the caller

    TreeNode

    PropertyTypeDefaultDescription
    keystring—Required. Unique key, required
    titleReactNode—Required. Node content, required
    childrenreadonly TreeNode[]—Child nodes using this same structure
    iconReactNode—Custom icon
    disabledboolean—Disable interaction.
    selectableboolean—Allow selection / display a checkbox; enabled by default
    checkableboolean—Allow selection / display a checkbox; enabled by default
    disableCheckboxboolean—Disable checks and stop check propagation
    searchLabelstring—Search and accessible text for non-text titles
    isLeafboolean—false allows asynchronously loaded children

    TreeSelectInfo

    PropertyTypeDefaultDescription
    nodeTreeNode—Required. Operated node
    selectedboolean—Required. Whether the node is now selected
    selectedNodesreadonly TreeNode[]—Required. All selected nodes

    TreeCheckInfo

    PropertyTypeDefaultDescription
    nodeTreeNode—Required. Operated node
    checkedboolean—Required. Whether the node is now checked
    checkedNodesreadonly TreeNode[]—Required. All fully checked nodes
    halfCheckedKeysreadonly string[]—Required. Partially checked keys

    TreeExpandInfo

    PropertyTypeDefaultDescription
    nodeTreeNode—Required. Operated node
    expandedboolean—Required. Whether the node is now expanded

    Types

    Import these types to define props or prepare data.

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

    Usage notes

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

    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

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

    TreeDropInfo API

    PropertyTypeDefaultDescription
    nodeTreeNodeRequiredRequired. Operated node
    targetTreeNodeRequiredRequired. Target node receiving the drop.
    position'before' | 'inside' | 'after'RequiredRequired. Place before, inside or after the target node.
    dataTreeNode[]RequiredRequired. 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.

    import { moveTreeNode } from '@ifhover/leaf-ui';
    
    const next = moveTreeNode(data, 'design', 'team', 'inside');
    ParameterTypeDescription
    datareadonly TreeNode[]Source tree
    keystringNode key to move
    targetstringTarget node key
    position'before' | 'inside' | 'after'Position relative to the target
    Return valueTreeNode[] | nullNew tree, or null for an invalid move