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-select.md.
  • English
  • TreeSelect

    Choose one or more values from hierarchical data.

    Current value: None
    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>
      );
    }
    

    Multiple selection and checks

    Visual design
    Visual design
    Design
    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>
      );
    }
    

    States and panel width

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

    Extended features

    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 },
            ];
          }}
        />
      );
    }
    

    Selection limits and node rendering

    Choose up to two people; keep one tag visible
    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
              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>
      );
    }
    

    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.

    Visual designProduct designFrontendBackend
    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>
      );
    }
    

    TreeSelect API

    PropertyTypeDefaultDescription
    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.
    popupClassNamestring—Custom class for the popup.
    popupStyleCSSProperties—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.
    optionsreadonly TreeSelectOption[]RequiredRequired. Hierarchical options
    checkStrictlybooleanfalseCheck parents and children independently
    showCheckedStrategy'child' | 'parent' | 'all''child'Display and output children, consolidated parents or all checked nodes
    showSearchbooleantrueFilter nodes by typing
    onSearch(query: string) => void—Search changes
    allowClearbooleantrueAllow clearing enabled selections
    defaultExpandAllbooleanfalseExpand all initially
    treeDefaultExpandedKeysreadonly string[][]Values expanded initially
    size'sm' | 'md' | 'lg''md'Control size
    status'error' | 'warning'—Validation status
    popupWidthnumber | stringContent widthPanel width / maximum width; at least as wide as the trigger
    popupMaxWidthnumber | string420Panel width / maximum width; at least as wide as the trigger
    notFoundContentReactNodeNo optionsContent when no options match
    onOpenChange(open: boolean) => void—Open state changes
    loadData( option: TreeSelectOption, signal: AbortSignal, ) => Promise<readonly TreeSelectOption[]>—Load children on expansion; signal supports cancellation
    onLoadError(error: unknown, option: TreeSelectOption) => void—Loading failed; expand again to retry
    virtualbooleanfalseRender visible items only
    listHeightnumber256List height
    itemHeightnumber38Estimated virtual row height including spacing; follows the regional height and density. Actual rows are measured.
    inputReadOnlyboolean—Disable typing while retaining panel selection.
    openboolean—Control whether the panel is open.
    defaultOpenboolean—Initial open state.
    maxTagCountnumber | 'responsive'—Maximum visible tags; responsive collapses them to the available width.
    tagWrapbooleantrueAllow selected tags to wrap; false keeps one line and collapses overflow.
    maxCountnumber—Maximum selection count.
    treeExpandedKeysreadonly string[]—Controlled expanded nodes.
    onExpand(keys: string[]) => void—Called when expanded nodes change.
    filterTreeNode(option: TreeSelectOption, query: string) => boolean—Customize search matching for nodes.
    nodeRender(option: TreeSelectOption) => ReactNode—Customize node content.
    cacheKeystring | number—Changing the data version clears lazy caches and cancels stale requests.
    multiplefalse | true | booleanfalseMultiple / checkbox mode, which automatically enables multiple selection
    treeCheckablefalse | boolean | truefalseMultiple / checkbox mode, which automatically enables multiple selection
    valuestring | null | readonly string[] | readonly string[]nullControlled / initial value; use an array for multiple selection
    defaultValuestring | null | readonly string[] | readonly string[]nullControlled / 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
    disabledbooleanfalseDisable interaction.
    requiredbooleanfalseRequire a non-empty value; compound controls validate once.
    namestring—Field name in native FormData.
    formstring—ID of the associated native form.

    TreeSelectOption

    PropertyTypeDefaultDescription
    valuestring—Required. Unique value, required
    labelReactNode—Required. Display label, required
    childrenreadonly TreeSelectOption[]—Child options using the same structure
    disabledboolean—Disable interaction.
    selectableboolean—Set false to make a category non-selectable
    disableCheckboxboolean—Disable checks and stop propagation
    iconReactNode—Node icon
    searchLabelstring—Search and accessible text for non-text labels
    isLeafboolean—Identify a leaf node, including in lazy data.

    Types

    Import these types to define props or prepare data.

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

    Usage notes

    Option values must be unique and stable. Example data:

    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.