Navigation · Tree

Tree

A tree view component presents a hierarchical list.

Demos

How to import

<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';
</script>

Basic Usage

By default, tree is in single select mode and each item is selectable.

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
            key: '1',
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree defaultExpandAll :style="style" :treeData="treeData" />
</template>

Multi-choice

You could use multiple to set mode to multi-choice. When all child items are selected, the parent item will be selected.

  • Asia
  • China
  • Beijing
  • Shanghai
  • Chengdu
  • Japan
  • Osaka
  • North America
  • United States
  • Canada
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        { label: 'Beijing', value: 'Beijing', key: '0-0-0' },
                        { label: 'Shanghai', value: 'Shanghai', key: '0-0-1' },
                        { label: 'Chengdu', value: 'Chengdu', key: '0-0-2' },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [{ label: 'Osaka', value: 'Osaka', key: '0-1-0' }],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                { label: 'United States', value: 'United States', key: '1-0' },
                { label: 'Canada', value: 'Canada', key: '1-1' },
            ],
        },
    ];

    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree defaultExpandAll multiple :style="style" :treeData="treeData" />
</template>

Searchable

Use filterTreeNode to support search input. By default it searches the label property of the data. You can use treeNodeFilterProp to set another property to search or pass in a function to filterTreeNode to customize search behavior.

You could also use showFilteredOnly if you prefer to display filtered results only.

showFilteredOnly

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Switch, Tree } from '@fibrous/semi-ui-vue';

    const showFilteredOnly = ref(false);

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        { label: 'Beijing', value: 'Beijing', key: '0-0-0' },
                        { label: 'Shanghai', value: 'Shanghai', key: '0-0-1' },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [{ label: 'Osaka', value: 'Osaka', key: '0-1-0' }],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                { label: 'United States', value: 'United States', key: '1-0' },
                { label: 'Canada', value: 'Canada', key: '1-1' },
            ],
        },
    ];

    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <span id="tree-searchable-filtered-only">showFilteredOnly</span>
    <Switch v-model="showFilteredOnly" aria-labelledby="tree-searchable-filtered-only" size="small" />
    <br />
    <Tree filterTreeNode multiple :showFilteredOnly="showFilteredOnly" :style="style" :treeData="treeData" />
</template>

After setting the filterTreeNode property to enable search, you can customize the search box with the #search slot, whose parameters are class, modelValue, placeholder, showClear and onChange. An empty slot (<template #search />) hides the search box.

Search
Code
<script setup lang="ts">
    import { Input, Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        { label: 'Beijing', value: 'Beijing', key: '0-0-0' },
                        { label: 'Shanghai', value: 'Shanghai', key: '0-0-1' },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [{ label: 'Osaka', value: 'Osaka', key: '0-1-0' }],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                { label: 'United States', value: 'United States', key: '1-0' },
                { label: 'Canada', value: 'Canada', key: '1-1' },
            ],
        },
    ];
</script>

<template>
    <Tree filterTreeNode :treeData="treeData">
        <template #search="{ class: searchClass, modelValue, placeholder, showClear, onChange }">
            <Input :class="searchClass" :modelValue="modelValue" :placeholder="placeholder" :showClear="showClear" @change="onChange">
                <template #prefix>Search</template>
            </Input>
        </template>
    </Tree>
</template>

Trigger search manually

Use a template ref to get the Tree instance, and call its search method to trigger search manually. Note that you need to set filterTreeNode to enable search at the same time. If the search box is outside the Tree, you can hide the search box inside it with an empty #search slot.

JSON TreeData

You could use treeDataSimpleJson to pass in treeNodes data in JSON format. In this case, key will be used as key and label, and value will be used as value correspondingly. Return value includes JSON data in selected nodes.

  • Node2
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const json = {
        Node1: {
            'Child Node1': '0-0-1',
            'Child Node2': '0-0-2',
        },
        Node2: '0-1',
    };

    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree multiple :style="style" :treeDataSimpleJson="json" @change="(value) => console.log('当前所有选中项: ', value)" @select="(key) => console.log('当前选项: ', key)" />
</template>

BlockNode

You could use blockNode to set node to display as a row. In this case, styles for hovering and selected state take effects on the entire row. By default, it is set to true. When it is set to false, only label will be highlighted.

  • Asia
  • China
  • Beijing
  • Shanghai

  • Asia
  • China
  • Beijing
  • Shanghai
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];
</script>

<template>
    <div>
        <Tree :blockNode="false" defaultValue="Shanghai" :treeData="treeData" />
        <br />
        <Tree :blockNode="false" defaultValue="Shanghai" multiple :treeData="treeData" />
    </div>
</template>

Custom TreeNode Label

The label of TreeNodeData accepts only a string or a number. Custom node content comes from the #label slot, whose parameters are item (the node data), label and searchWord (the current search word). Search by filterTreeNode still matches the label text in treeData.

Node content is best kept for display. Buttons and links in a node can be reached with Tab (see Accessibility), but screen readers read them as part of the node's name and arrow-key browsing skips them. Put actions on a node outside the tree, for example in a menu opened by @contextMenu or in a toolbar next to the tree.

Code
<script setup lang="ts">
    import { Tag, Tree } from '@fibrous/semi-ui-vue';

    // Node content is display-only: focus stays on the tree, so buttons and links inside are keyboard-unreachable
    const treeData = [
        {
            label: '亚洲',
            value: 'yazhou',
            key: 'yazhou',
            children: [
                {
                    label: '中国',
                    value: 'zhongguo',
                    key: 'zhongguo',
                },
                {
                    label: '日本',
                    value: 'riben',
                    key: 'riben',
                },
            ],
        },
    ];
    const labelStyle = {
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
    };
    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree :style="treeStyle" :treeData="treeData">
        <template #label="{ item, label }">
            <div :style="labelStyle">
                <span>{{ label }}</span>
                <Tag size="small">{{ item.children ? '大洲' : '国家' }}</Tag>
            </div>
        </template>
    </Tree>
</template>

Ellipsis for long labels. Truncate overlong text with an ellipsis inside the #label slot.

Code
<script setup lang="ts">
    import { Tree, Typography } from '@fibrous/semi-ui-vue';

    const { Text } = Typography;

    const treeData = [
        {
            label: '亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲亚洲',
            value: 'yazhou',
            key: 'yazhou',
            children: [
                {
                    label: '中国中国中国中国中国中国中国中国中国中国中国中国',
                    value: 'zhongguo',
                    key: 'zhongguo',
                },
                {
                    label: '日本日本日本日本日本日本日本日本日本日本日本日本',
                    value: 'riben',
                    key: 'riben',
                },
            ],
        },
    ];
    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree :style="treeStyle" :treeData="treeData">
        <template #label="{ label }">
            <Text :ellipsis="{ showTooltip: true }" :style="{ width: '100%' }">{{ label }}</Text>
        </template>
    </Tree>
</template>

Custom Icon

Use the #icon slot to add a customized icon. Its parameters are item, level, expanded, isLeaf, loading, selected, checked and halfChecked, so the icon can differ by node. TreeNodeData has no icon field.

Code
<script setup lang="ts">
    import { IconMapPin } from '@fibrous/semi-icons-vue';
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                },
            ],
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree :style="style" :treeData="treeData">
        <template #icon>
            <IconMapPin :style="{ color: 'var(--semi-color-text-2)' }" />
        </template>
    </Tree>
</template>

Directory

You could use directory to display tree as a directory with default icons. Also, the icon could be overwritten by custom Icon.

Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree directory :style="style" :treeData="treeData" />
</template>

Disabled

You can use disableStrictly to set whether to enable strict disabling. After enabling strict disabling, when the node is disabled, the selected state cannot be changed through the relationship between the child or the parent.

  • Asia
  • China
  • Beijing
  • Shanghai
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                            disabled: true,
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                            disabled: true,
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree defaultValue="Shanghai" disableStrictly multiple :style="style" :treeData="treeData" />
</template>

Checked RelationShip

Version: >= 2.5.0

When multiple selections are made, checkRelation can be used to set the type of node selection relationship, optional: 'related' (default), 'unRelated'. When the selection relationship is 'unRelated', it means that selections between nodes do not affect each other.

  • Asia
  • China
  • Beijing
  • Shanghai
  • Chengdu
  • Japan
  • Osaka
  • North America
  • United States
  • Canada
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                        {
                            label: 'Chengdu',
                            value: 'Chengdu',
                            key: '0-0-2',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree checkRelation="unRelated" defaultExpandAll multiple :style="style" :treeData="treeData" />
</template>

Default Expand All

Both defaultExpandAll and expandAll can set the default expanded/collapsed state of Tree. The difference between the two is that defaultExpandAll only takes effect at initialization, while expandAll will not only take effect at initialization, but also when the data (treeData/treeDataSimpleJson) is dynamically updated, expandAll will still take effect.

Among them, expandAll is supported starting from 1.30.0.

In the demo below, after clicking the button to update TreeData, defaultExpandAll becomes invalid, and expandAll still takes effect.

defaultExpandAll
  • Node1
  • Child Node1
  • Child Node2
  • Node2
expandAll
  • Node1
  • Child Node1
  • Child Node2
  • Node2
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, Tree, type TreeProps } from '@fibrous/semi-ui-vue';

    const json = {
        Node1: {
            'Child Node1': '0-0-1',
            'Child Node2': '0-0-2',
        },
        Node2: '0-1',
    };
    const json2 = {
        Node3: {
            'Child Node1': '0-0-1',
            'Child Node2': '0-0-2',
            'Child Node3': '0-0-3',
            'Child Node4': '0-0-4',
        },
        Node2: '0-1',
    };
    const style = {
        marginRight: '20px',
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
    const tree = ref<TreeProps['treeDataSimpleJson']>(json);
</script>

<template>
    <Button :style="{ marginBottom: '10px' }" @click="tree = json2">点击更新 TreeData</Button>
    <div :style="{ display: 'flex' }">
        <div>
            <span>defaultExpandAll</span>
            <Tree defaultExpandAll :style="style" :treeDataSimpleJson="tree" />
        </div>
        <div>
            <span>expandAll</span>
            <Tree expandAll :style="style" :treeDataSimpleJson="tree" />
        </div>
    </div>
</template>

When expandedKeys is passed in, it is the expanded controlled component, which can be bound with v-model:expandedKeys or used with @expand. When the expansion is controlled, if you enable filterTreeNode and search, the node will not be automatically expanded. At this time, the expansion of the node is completely controlled by expandedKeys. You can use the second parameter filteredExpandedKeys of @search to realize the search expansion effect when the expansion is controlled.

Controlled Component

When modelValue is passed in, Tree is a controlled component. Bind it with v-model, or use it along with @change.

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Tree, type TreeValue } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
            key: '1',
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
    const value = ref<TreeValue>('Shanghai');
</script>

<template>
    <Tree v-model="value" :style="style" :treeData="treeData" />
</template>

Auto Expand Parent

In the case of controlled expansion, when autoExpandParent is turned on, if you want to collapse the parent element, you need to collapse all its child elements. By default, autoExpandParent is false, that is, the collapse of the parent element is not affected by the child element.

需要先将“中国”节点收起后,才能够收起“亚洲”节点

  • 亚洲
  • 中国
  • 北京
  • 上海
  • 北美洲
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
            key: '1',
        },
    ];
    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
    const expandedKeys = ref(['0', '0-0']);
</script>

<template>
    <div>需要先将“中国”节点收起后,才能够收起“亚洲”节点</div>
    <br />
    <Tree v-model:expandedKeys="expandedKeys" autoExpandParent :style="style" :treeData="treeData" />
</template>

Custom expansion icon

The expansion Icon can be customized through the #expandIcon slot, whose parameters are onClick, class and expanded. When the slot returns a single element, that element automatically gets the class and the click that expands; the parameters can also be bound by hand.

<Tree :treeData="treeData">
    <template #expandIcon="{ expanded, onClick, class: className }">
        <IconMinus v-if="expanded" :class="className" @click="onClick" />
        <IconPlus v-else :class="className" @click="onClick" />
    </template>
</Tree>

Examples are as follows:

expandIcon 是固定的图标

  • 亚洲
  • 日本
  • 北美洲

expandIcon 随展开状态变化

  • 亚洲
  • 日本
  • 北美洲
Code
<script setup lang="ts">
    import { IconChevronDown, IconMinus, IconPlus } from '@fibrous/semi-icons-vue';
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '亚洲',
            key: 'yazhou',
            children: [
                {
                    label: '中国',
                    key: 'zhongguo',
                    children: [
                        {
                            label: '北京',
                            key: 'beijing',
                        },
                        {
                            label: '上海',
                            key: 'shanghai',
                        },
                    ],
                },
                {
                    label: '日本',
                    key: 'riben',
                },
            ],
        },
        {
            label: '北美洲',
            key: 'beimeizhou',
        },
    ];
    const style = {
        width: '260px',
        height: '200px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <p>expandIcon 是固定的图标</p>
    <Tree :defaultExpandedKeys="['yazhou']" multiple :style="style" :treeData="treeData">
        <template #expandIcon>
            <IconChevronDown class="testCls" size="small" />
        </template>
    </Tree>
    <br />
    <p>expandIcon 随展开状态变化</p>
    <Tree :defaultExpandedKeys="['yazhou']" multiple :style="style" :treeData="treeData">
        <template #expandIcon="{ expanded, onClick, class: className }">
            <IconMinus v-if="expanded" :class="className" size="small" @click="onClick" />
            <IconPlus v-else :class="className" size="small" @click="onClick" />
        </template>
    </Tree>
</template>

Tree with line

Set the line between nodes through showLine, the default is false, supported starting from 2.50.0

showLine
  • parent-0
  • leaf-0-0
  • leaf-0-0-0
  • leaf-0-0-1
  • leaf-0-0-2
  • leaf-0-1
  • parent-1
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Switch, Tree } from '@fibrous/semi-ui-vue';

    const show = ref(true);

    const treeData = [
        {
            label: 'parent-0',
            key: 'parent-0',
            children: [
                {
                    label: 'leaf-0-0',
                    key: 'leaf-0-0',
                    children: [
                        { label: 'leaf-0-0-0', key: 'leaf-0-0-0' },
                        { label: 'leaf-0-0-1', key: 'leaf-0-0-1' },
                        { label: 'leaf-0-0-2', key: 'leaf-0-0-2' },
                    ],
                },
                { label: 'leaf-0-1', key: 'leaf-0-1' },
            ],
        },
        { label: 'parent-1', key: 'parent-1' },
    ];

    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <div :style="{ display: 'flex', alignItems: 'center', columnGap: '5px', marginBottom: '5px' }">
        <strong id="tree-line-show-line">showLine </strong>
        <Switch v-model="show" aria-labelledby="tree-line-show-line" />
    </div>
    <Tree defaultExpandAll :showLine="show" :style="style" :treeData="treeData" />
</template>

Virtualized Tree

If you need to render large sets of tree structured data, you could use virtualized tree. In virtualized mode, animation / motion is disabled for better performance.

The property virtualize is an object consisting of the following values:

  • height: Height of the tree, If passed in as a string, computed height should not be zero for render purpose, in other words, parent node should have offsetHeight
  • width: Width of the tree.
  • itemSize: Height for each line of treeNode, required

If tree is searchable, you could also turn on showFilteredOnly to reduce time of rendering for results.

共 0 个节点

Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, Tree, type TreeNodeData } from '@fibrous/semi-ui-vue';

    const style = {
        width: '260px',
        border: '1px solid var(--semi-color-border)',
    };

    const virtualize = {
        // A set Tree height fills the remaining space
        height: 300,
        itemSize: 28,
    };

    function generateData(x = 5, y = 4, z = 3, data: TreeNodeData[] = []) {
        // x: nodes per level. y: nodes with children per level. z: depth (0 is one level)
        function fill(level: number, preKey: string, nodes: TreeNodeData[]) {
            const parents: string[] = [];
            for (let i = 0; i < x; i++) {
                const key = `${preKey}-${i}`;
                nodes.push({ label: `${key}-标签`, key: `${key}-key`, value: `${key}-value` });
                if (i < y) {
                    parents.push(key);
                }
            }
            if (level < 0) {
                return;
            }
            parents.forEach((key, index) => {
                const children: TreeNodeData[] = [];
                nodes[index].children = children;
                fill(level - 1, key, children);
            });
        }
        fill(z, '0', data);

        let total = 0;
        for (let n = z + 1; n >= 0; n--) {
            total += x * y ** n;
        }
        return { gData: data, total };
    }

    const gData = ref<TreeNodeData[]>([]);
    const total = ref(0);

    const onGen = () => {
        const data = generateData();
        gData.value = data.gData;
        total.value = data.total;
    };
</script>

<template>
    <div :style="{ padding: '0 20px' }">
        <Button @click="onGen">生成数据: </Button>
        <span>共 {{ total }} 个节点</span>
        <br />
        <br />
        <Tree v-if="gData.length" filterTreeNode showFilteredOnly :style="style" :treeData="gData" :virtualize="virtualize" />
    </div>
</template>

Dynamic Update of Data

treeData is replaced as a whole: only a new array updates the tree, while changes made in place to the array or a node are not seen.

  • item-0

Code
<script setup lang="ts">
    import { shallowRef } from 'vue';
    import { Button, Tree, type TreeNodeData } from '@fibrous/semi-ui-vue';

    const style = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };

    const treeData = shallowRef<TreeNodeData[]>([
        {
            key: '0',
            label: 'item-0',
            value: '0',
        },
    ]);

    function add() {
        const itemLength = Math.floor(Math.random() * 5) + 1;
        treeData.value = Array.from({ length: itemLength }).map((_, i) => {
            const length = Math.floor(Math.random() * 3);
            const children = Array.from({ length }).map((_, ci) => ({
                key: `${i}-${ci}`,
                label: `Leaf-${i}-${ci}`,
                value: `${i}-${ci}`,
            }));
            return {
                key: `${i}`,
                label: `Item-${i}`,
                value: `${i}`,
                children,
            };
        });
    }
</script>

<template>
    <div :style="style">
        <Tree :treeData="treeData" />
        <br />
        <Button :style="{ margin: '20px' }" @click="add">动态改变数据</Button>
    </div>
</template>

Load Data Asynchronously

You could use loadData to load treeData asynchronously on node expansion. Notice isLeaf is required to mark node as leaf in treeData.

  • Leaf Node
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Tree, type TreeNodeData } from '@fibrous/semi-ui-vue';

    const treeData = ref<TreeNodeData[]>([
        { label: 'Expand to load', value: '0', key: '0' },
        { label: 'Expand to load', value: '1', key: '1' },
        { label: 'Leaf Node', value: '2', key: '2', isLeaf: true },
    ]);

    function updateTreeData(list: TreeNodeData[], key: string | undefined, children: TreeNodeData[]): TreeNodeData[] {
        return list.map((node) => {
            if (node.key === key) {
                return { ...node, children };
            }
            if (node.children) {
                return { ...node, children: updateTreeData(node.children, key, children) };
            }
            return node;
        });
    }

    function onLoadData(treeNode?: TreeNodeData) {
        const key = treeNode?.key;
        return new Promise<void>((resolve) => {
            if (treeNode?.children) {
                resolve();
                return;
            }
            setTimeout(() => {
                treeData.value = updateTreeData(treeData.value, key, [
                    { label: 'Child Node', key: `${key}-0` },
                    { label: 'Child Node', key: `${key}-1` },
                ]);
                resolve();
            }, 1000);
        });
    }
</script>

<template>
    <Tree :loadData="onLoadData" :treeData="treeData" />
</template>

Draggable Tree

You could use draggable along with @drop to achieve a draggable Tree.

The payload of each drag event is one object with the following fields:

- @dragEnd: { event, node: TreeDragNode }
- @dragEnter: { event, node: TreeDragNode, expandedKeys?: string[] }
- @dragLeave: { event, node: TreeDragNode }
- @dragOver: { event, node: TreeDragNode }
- @dragStart: { event, node: TreeDragNode }
- @drop: { event, node: TreeDragNode, dragNode: TreeDragNode, dragNodesKeys: string[], dropPosition: number, dropToGap: boolean }

The data type TreeDragNode, in addition to all the attributes of TreeNodeData, also includes expanded and pos attributes,

TreeDragNode {
    expanded: boolean,
    pos: string
    value?: string | number;
    label?: string | number;
    disabled?: boolean;
    isLeaf?: boolean;
    [key: string]: any;
}
  • pos refers to the positional relationship of the current node in the entire treeData, such as the 0th node of the 2nd node of the 1st node of the 0th layer: '0-1-2-0'
  • dropPosition refers to the position where the dragged node is dropped in the current hierarchy. If it is inserted before the 0th node of the same level, it will be -1, and after the 0th node, it will be 1, and it will fall on it. is 0, and so on. With dropToGap, a more complete judgment can be obtained.
  • dropToGap refers to whether the dragged node is dropped between nodes, if false, it is dropped above a node
  • Europe
Code
<script setup lang="ts">
    import { shallowRef } from 'vue';
    import { Tree, type TreeNodeData, type TreeOnDragProps } from '@fibrous/semi-ui-vue';

    type LoopCallback = (item: TreeNodeData, ind: number, arr: TreeNodeData[]) => void;

    const treeData = shallowRef<TreeNodeData[]>([
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
        {
            label: 'Europe',
            value: 'Europe',
            key: '2',
        },
    ]);

    function onDrop(info: TreeOnDragProps) {
        const { dropToGap, node, dragNode } = info;
        const dropKey = node.key;
        const dragKey = dragNode.key;
        const dropPos = node.pos.split('-');
        const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);

        const data = [...treeData.value];
        const loop = (data: TreeNodeData[], key: string | undefined, callback: LoopCallback) => {
            data.forEach((item, ind, arr) => {
                if (item.key === key) return callback(item, ind, arr);
                if (item.children) return loop(item.children, key, callback);
            });
        };
        let dragObj: TreeNodeData = {};
        loop(data, dragKey, (item, ind, arr) => {
            arr.splice(ind, 1);
            dragObj = item;
        });

        if (!dropToGap) {
            loop(data, dropKey, (item) => {
                item.children = item.children || [];
                item.children.push(dragObj);
            });
        } else if (dropPosition === 1 && node.children && node.expanded) {
            // Expanded with children and dropped in the bottom gap: insert at the top
            loop(data, dropKey, (item) => {
                item.children = item.children || [];
                item.children.unshift(dragObj);
            });
        } else {
            let dropNodeInd = 0;
            let dropNodePosArr: TreeNodeData[] = [];
            loop(data, dropKey, (_item, ind, arr) => {
                dropNodePosArr = arr;
                dropNodeInd = ind;
            });
            if (dropPosition === -1) {
                dropNodePosArr.splice(dropNodeInd, 0, dragObj);
            } else {
                dropNodePosArr.splice(dropNodeInd + 1, 0, dragObj);
            }
        }
        treeData.value = data;
    }
</script>

<template>
    <Tree draggable :treeData="treeData" @drop="onDrop" />
</template>

Advanced FullRender

v>=1.7.0

You could use the #fullLabel slot for advanced rendering to render the content of the entire row on your own.

The row element (li) carrying the id, role="treeitem" and the row state is provided by Tree itself, and so is the positioning style of a virtualized tree; the slot holds only what is inside the row. Bind the class parameter to the content element and it gets the same indentation, selection and focus styles and the same inline padding as a built-in row.

The slot parameter types are as follows:

type TreeFullLabelSlotProps = {
    /* The original data of the row */
    data: TreeNodeData;
    /* The level of the line can be used to customize the indentation value */
    level: number;
    /* Class name, including built-in styles such as indentation, selection, focus and drag state */
    class: string;
    /* The built-in expand button */
    expandIcon: VNode;
    /* Selected state */
    checkStatus: {
        /* Whether to select in the multi-select state */
        checked: boolean;
        /* Whether to half-select in the multi-select state */
        halfChecked: boolean;
    };
    /* Expand status */
    expandStatus: {
        /* Has it been expanded */
        expanded: boolean;
        /* Is it unfolding */
        loading: boolean;
    };
    /* Whether the node meets the search conditions */
    filtered: boolean;
    /* Current search box input */
    searchWord: string;
    /* Click the callback of the entire row: selects it, or checks it in a multiple tree */
    onClick: (event: MouseEvent) => void;
    /* Multi-select click callback */
    onCheck: (event: MouseEvent) => void;
    /* expand callback */
    onExpand: (event: MouseEvent) => void;
    /* Right-click the callback for the entire row */
    onContextmenu: (event: MouseEvent) => void;
    /* Double-click the entire line of callback */
    onDblclick: (event: MouseEvent) => void;
};
<Tree :treeData="treeData">
    <template #fullLabel="{ data, class: className, onClick }">
        <div :class="className" @click="onClick">{{ data.label }}</div>
    </template>
</Tree>

Here are some demos.

First is to render Parent node as separator and only allow leaf nodes to be selected.
#fullLabel only takes care of the UI rendering and won't affect inside data logic. But you could choose info to your needs and use it with controlled mode for advanced usage.

Code
<script setup lang="ts">
    import { Checkbox, Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        {
                            label: 'Beijing',
                            value: 'Beijing',
                            key: '0-0-0',
                        },
                        {
                            label: 'Shanghai',
                            value: 'Shanghai',
                            key: '0-0-1',
                        },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [
                        {
                            label: 'Osaka',
                            value: 'Osaka',
                            key: '0-1-0',
                        },
                    ],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                {
                    label: 'United States',
                    value: 'United States',
                    key: '1-0',
                },
                {
                    label: 'Canada',
                    value: 'Canada',
                    key: '1-1',
                },
            ],
        },
    ];

    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree leafOnly multiple :style="treeStyle" :treeData="treeData">
        <!-- Focus stays on the tree (arrows expand, Enter/Space check), so the inline checkbox is display-only -->
        <template #fullLabel="{ class: cls, data, checkStatus, expandIcon, onCheck, onExpand }">
            <div :class="cls" @click="data.children?.length ? onExpand($event) : onCheck($event)">
                <component :is="expandIcon" v-if="data.children?.length" />
                <Checkbox v-else aria-hidden="true" :indeterminate="checkStatus.halfChecked" :modelValue="checkStatus.checked" :style="{ marginRight: '8px' }" tabindex="-1" />
                <span>{{ data.label }}</span>
            </div>
        </template>
    </Tree>
</template>

The second is for the scenario of "I hope that only leaf nodes can be single-selected, and the parent node only plays a role in grouping".

  • You only need to click on the parent node without triggering the selection, click on the leaf node to trigger.
Code
<script setup lang="ts">
    import { Tree } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: 'Asia',
            value: 'Asia',
            key: '0',
            children: [
                {
                    label: 'China',
                    value: 'China',
                    key: '0-0',
                    children: [
                        { label: 'Beijing', value: 'Beijing', key: '0-0-0' },
                        { label: 'Shanghai', value: 'Shanghai', key: '0-0-1' },
                    ],
                },
                {
                    label: 'Japan',
                    value: 'Japan',
                    key: '0-1',
                    children: [{ label: 'Osaka', value: 'Osaka', key: '0-1-0' }],
                },
            ],
        },
        {
            label: 'North America',
            value: 'North America',
            key: '1',
            children: [
                { label: 'United States', value: 'United States', key: '1-0' },
                { label: 'Canada', value: 'Canada', key: '1-1' },
            ],
        },
    ];

    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree :style="treeStyle" :treeData="treeData" @change="(...args) => console.log('change', ...args)">
        <template #fullLabel="{ class: rowClass, data, expandIcon, onClick, onExpand }">
            <div :class="rowClass" @click="data.children?.length ? onExpand($event) : onClick($event)">
                <component :is="expandIcon" />
                <span>{{ data.label }}</span>
            </div>
        </template>
    </Tree>
</template>

Third is for the scenario when selecting Parent node also highlighting child node.

  • 黑色固定按钮
  • 模块
  • 可自由摆放的组件
  • 分栏容器
  • 按钮组件
  • 按钮组件
  • 模块
  • 自定义组件
Code
<script setup lang="ts">
    import { shallowRef, type Component } from 'vue';
    import { IconAbsoluteStroked, IconComponentStroked, IconFixedStroked, IconInnerSectionStroked, IconSectionStroked } from '@fibrous/semi-icons-vue';
    import { Tree, type TreeNodeData } from '@fibrous/semi-ui-vue';

    const icons: Record<string, Component> = {
        'fix-btn-0': IconFixedStroked,
        'module-0': IconSectionStroked,
        'free-compo-0': IconAbsoluteStroked,
        'split-col-0': IconInnerSectionStroked,
        'btn-0': IconComponentStroked,
        'btn-1': IconComponentStroked,
        'module-1': IconSectionStroked,
        'cus-0': IconComponentStroked,
    };
    const iconStyle = { marginRight: '8px', color: 'var(--semi-color-text-2)' };

    const selected = shallowRef(new Set<string | undefined>());
    const selectedThroughParent = shallowRef(new Set<string | undefined>());
    const treeData = [
        {
            label: '黑色固定按钮',
            key: 'fix-btn-0',
        },
        {
            label: '模块',
            key: 'module-0',
            children: [
                {
                    label: '可自由摆放的组件',
                    key: 'free-compo-0',
                },
                {
                    label: '分栏容器',
                    key: 'split-col-0',
                    children: [
                        {
                            label: '按钮组件',
                            key: 'btn-0',
                        },
                        {
                            label: '按钮组件',
                            key: 'btn-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '模块',
            key: 'module-1',
            children: [
                {
                    label: '自定义组件',
                    key: 'cus-0',
                },
            ],
        },
    ];

    function findDescendantKeys(node: TreeNodeData) {
        const res = [node.key];
        const findChild = (item?: TreeNodeData) => {
            if (!item) return;
            const { children } = item;

            if (children && children.length) {
                children.forEach((child) => {
                    res.push(child.key);
                    findChild(child);
                });
            }
        };
        findChild(node);
        return res;
    }

    function onSelect(key: string, _selected: boolean, node: TreeNodeData) {
        selected.value = new Set([key]);
        selectedThroughParent.value = new Set(findDescendantKeys(node));
    }

    function rowBackground(key: string | undefined) {
        if (selected.value.has(key)) return 'var(--semi-blue-0)';
        if (selectedThroughParent.value.has(key)) return 'rgb(from var(--semi-blue-0) r g b / .5)';
        return 'transparent';
    }

    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree defaultExpandAll :style="treeStyle" :treeData="treeData" @select="onSelect">
        <template #fullLabel="{ class: cls, data, expandIcon, onClick }">
            <div :class="cls" :style="{ backgroundColor: rowBackground(data.key) }" @click="onClick">
                <span v-if="!data.children?.length" :style="{ width: '24px' }" />
                <component :is="expandIcon" v-else />
                <component :is="icons[data.key ?? '']" :style="iconStyle" />
                <span>{{ data.label }}</span>
            </div>
        </template>
    </Tree>
</template>

Advanced FullRender with Draggable

Draggable (draggable) and advanced customization (#fullLabel) can be used at the same time.

  • 黑色固定按钮
  • 模块
  • 可自由摆放的组件
  • 分栏容器
  • 按钮组件
  • 按钮组件
  • 模块
  • 自定义组件
Code
<script setup lang="ts">
    import { shallowRef, type Component } from 'vue';
    import { IconAbsoluteStroked, IconComponentStroked, IconFixedStroked, IconSectionStroked } from '@fibrous/semi-icons-vue';
    import { Tree, type TreeNodeData, type TreeOnDragProps } from '@fibrous/semi-ui-vue';

    type LoopCallback = (item: TreeNodeData, ind: number, arr: TreeNodeData[]) => void;

    const icons: Record<string, Component> = {
        'fix-btn-0': IconFixedStroked,
        'module-0': IconSectionStroked,
        'free-compo-0': IconAbsoluteStroked,
        'split-col-0': IconSectionStroked,
        'btn-0': IconComponentStroked,
        'btn-1': IconComponentStroked,
        'module-1': IconSectionStroked,
        'cus-0': IconComponentStroked,
    };
    const iconStyle = { marginRight: '8px', color: 'var(--semi-color-text-2)' };

    const selected = shallowRef(new Set<string | undefined>());
    const selectedThroughParent = shallowRef(new Set<string | undefined>());
    const treeData = shallowRef<TreeNodeData[]>([
        {
            label: '黑色固定按钮',
            key: 'fix-btn-0',
        },
        {
            label: '模块',
            key: 'module-0',
            children: [
                {
                    label: '可自由摆放的组件',
                    key: 'free-compo-0',
                },
                {
                    label: '分栏容器',
                    key: 'split-col-0',
                    children: [
                        {
                            label: '按钮组件',
                            key: 'btn-0',
                        },
                        {
                            label: '按钮组件',
                            key: 'btn-1',
                        },
                    ],
                },
            ],
        },
        {
            label: '模块',
            key: 'module-1',
            children: [
                {
                    label: '自定义组件',
                    key: 'cus-0',
                },
            ],
        },
    ]);

    function onDrop(info: TreeOnDragProps) {
        const { dropToGap, node, dragNode } = info;
        const dropKey = node.key;
        const dragKey = dragNode.key;
        const dropPos = node.pos.split('-');
        const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);

        const data = [...treeData.value];
        const loop = (data: TreeNodeData[], key: string | undefined, callback: LoopCallback) => {
            data.forEach((item, ind, arr) => {
                if (item.key === key) return callback(item, ind, arr);
                if (item.children) return loop(item.children, key, callback);
            });
        };

        let dragObj: TreeNodeData = {};
        loop(data, dragKey, (item, ind, arr) => {
            arr.splice(ind, 1);
            dragObj = item;
        });

        if (!dropToGap) {
            loop(data, dropKey, (item) => {
                item.children = item.children || [];
                item.children.push(dragObj);
            });
        } else if (dropPosition === 1 && node.children && node.expanded) {
            loop(data, dropKey, (item) => {
                item.children = item.children || [];
                item.children.unshift(dragObj);
            });
        } else {
            let dropNodeInd = 0;
            let dropNodePosArr: TreeNodeData[] = [];
            loop(data, dropKey, (_item, ind, arr) => {
                dropNodePosArr = arr;
                dropNodeInd = ind;
            });
            if (dropPosition === -1) {
                dropNodePosArr.splice(dropNodeInd, 0, dragObj);
            } else {
                dropNodePosArr.splice(dropNodeInd + 1, 0, dragObj);
            }
        }
        treeData.value = data;
    }

    function findDescendantKeys(node: TreeNodeData) {
        const res = [node.key];
        const findChild = (item?: TreeNodeData) => {
            if (!item) return;
            const { children } = item;

            if (children && children.length) {
                children.forEach((child) => {
                    res.push(child.key);
                    findChild(child);
                });
            }
        };
        findChild(node);
        return res;
    }

    function onSelect(key: string, _selected: boolean, node: TreeNodeData) {
        selected.value = new Set([key]);
        selectedThroughParent.value = new Set(findDescendantKeys(node));
    }

    function rowBackground(key: string | undefined) {
        if (selected.value.has(key)) return 'var(--semi-blue-0)';
        if (selectedThroughParent.value.has(key)) return 'rgb(from var(--semi-blue-0) r g b / .5)';
        return 'transparent';
    }

    const treeStyle = {
        width: '260px',
        height: '420px',
        border: '1px solid var(--semi-color-border)',
    };
</script>

<template>
    <Tree defaultExpandAll draggable :style="treeStyle" :treeData="treeData" @drop="onDrop" @select="onSelect">
        <template #fullLabel="{ class: cls, data, expandIcon, onClick }">
            <div :class="cls" :style="{ backgroundColor: rowBackground(data.key) }" @click="onClick">
                <span v-if="!data.children?.length" :style="{ width: '24px' }" />
                <component :is="expandIcon" v-else />
                <component :is="icons[data.key ?? '']" :style="iconStyle" />
                <span>{{ data.label }}</span>
            </div>
        </template>
    </Tree>
</template>

API Reference

Tree

Props

PropertyDescriptionTypeDefault
modelValueValue of the selected node (its key when it has no value), bound with v-model; an array when multiple. Passing it makes the tree controlled.TreeValueundefined
defaultValueThe initially selected items, read once on mount.TreeValuesemiGlobalDefault("Tree", "defaultValue")
treeDataNode data; keys are unique across the tree. Only a new array updates the tree, changes made in place are not seen.TreeNodeData[]semiGlobalDefault("Tree", "treeData")
treeDataSimpleJsonNode data as a plain JSON object; the value is a JSON object holding the selected nodes.TreeDataSimpleJsonsemiGlobalDefault("Tree", "treeDataSimpleJson")
keyMapsCustom field names for key, label and value in a node. When label is mapped and search is on, set treeNodeFilterProp to one of the keys in treeData or provide filterTreeNode.KeyMapPropssemiGlobalDefault("Tree", "keyMaps")
expandedKeysExpanded nodes, bound with v-model:expandedKeys. Passing it makes expansion fully controlled.string[]semiGlobalDefault("Tree", "expandedKeys")
defaultExpandedKeysNodes expanded initially, showing their direct children.string[]semiGlobalDefault("Tree", "defaultExpandedKeys")
defaultExpandAllExpands every node on mount. Later changes of treeData or treeDataSimpleJson do not affect it; use expandAll for that.booleansemiDefault("Tree", "defaultExpandAll", false)
expandAllExpands every node, and keeps doing so when the data changes.booleansemiDefault("Tree", "expandAll", false)
autoExpandParentWhether the parents of the nodes in expandedKeys expand automatically when expandedKeys changes; they always do on mount. When on, a controlled tree collapses a parent only after its expanded children are collapsed.booleansemiDefault("Tree", "autoExpandParent", false)
autoExpandWhenDragEnterWhether dragging over a collapsed node expands it.booleansemiDefault("Tree", "autoExpandWhenDragEnter", true)
autoMergeValueWhen a parent is selected the value leaves out its descendants; applies when leafOnly is false.booleansemiDefault("Tree", "autoMergeValue", true)
blockNodeShows each node as a whole row, highlighting the row on hover and selection; when off only the label is highlighted.booleansemiDefault("Tree", "blockNode", true)
checkRelationHow the checked states of nodes relate in a multiple tree: related links parents and children, unRelated keeps them independent.CheckRelationsemiDefault("Tree", "checkRelation", "related")
directoryDirectory mode with built-in folder icons, which the icon slot can replace.booleansemiDefault("Tree", "directory", false)
disabledDisables the whole tree. A node with disabled: false does not lift it.booleansemiDefault("Tree", "disabled", false)
disableStrictlyStrict disabling: a disabled node cannot change state through the selection of its parent or children.booleansemiDefault("Tree", "disableStrictly", false)
draggableWhether nodes can be dragged, used together with the drop event. It cannot be combined with virtualize.booleansemiDefault("Tree", "draggable", false)
hideDraggingNodeHides the drag image of the node being dragged.booleansemiDefault("Tree", "hideDraggingNode", false)
expandActionHow a node expands: false only through the arrow, click on a click, doubleClick on a double click. The keyboard does not use it.ExpandActionsemiDefault("Tree", "expandAction", false)
leafOnlyIn a multiple tree, the value and the payload of change hold leaf nodes only.booleansemiDefault("Tree", "leafOnly", false)
loadedKeysNodes already loaded, used with loadData and bound with v-model:loadedKeys.string[]semiGlobalDefault("Tree", "loadedKeys")
motionWhether expanding and collapsing are animated; animation is off for a virtualized tree.booleansemiDefault("Tree", "motion", true)
multipleWhether several nodes can be selected. A parent is checked automatically once all its children are.booleansemiDefault("Tree", "multiple", false)
changeWithObjectA boolean attribute, not an event: when true the payload of change is the node object with value, label and other fields instead of the value; a controlled modelValue and defaultValue must then be objects with a value.booleansemiDefault("Tree", "changeWithObject", false)
showClearWhether the search box shows a clear button.booleansemiDefault("Tree", "showClear", true)
showFilteredOnlyShows only the filtered nodes while searching.booleansemiDefault("Tree", "showFilteredOnly", false)
showLineShows connecting lines between nodes.booleansemiDefault("Tree", "showLine", false)
treeNodeFilterPropThe node field matched against the search input, label by default.stringsemiDefault("Tree", "treeNodeFilterProp", "label")
labelEllipsisWhether an overflowing label is truncated; on by default for a virtualized tree.booleansemiGlobalDefault("Tree", "labelEllipsis")
virtualizeList virtualization for many nodes: height, width and itemSize (required). Animation is off once enabled.VirtualizesemiGlobalDefault("Tree", "virtualize")
searchClassNameClass of the search box container.stringsemiGlobalDefault("Tree", "searchClassName")
searchPlaceholderPlaceholder of the search box.stringsemiGlobalDefault("Tree", "searchPlaceholder")
searchStyleStyle of the search box container.StyleValuesemiGlobalDefault("Tree", "searchStyle")
filterTreeNodeWhether search is on. true matches by treeNodeFilterProp; a function customizes the match, with data as the node.Typeboolean | ((inputValue: string, treeNodeString: string, data?: TreeNodeData | undefined) => boolean)undefined
loadDataLoads the children of a node and returns a Promise. Mark leaves with isLeaf in the data; a node without isLeaf shows the expand arrow while this is set.Type((treeNode?: TreeNodeData | undefined) => Promise<void>)undefined

Events

EventDescriptionCallback parameters
update:modelValueEmitted when the selection changes; used by v-model.[value: TreeValue]
changeEmitted when the selection changes, with the same payload as update:modelValue. It shadows the native event of the same name on the root element.[value: TreeValue]
update:expandedKeysEmitted when the expanded nodes change; used by v-model:expandedKeys.[expandedKeys: string[]]
expandEmitted when a node expands or collapses; info holds expanded and node.[expandedKeys: string[], info: TreeExpandedOtherProps]
update:loadedKeysEmitted when the loaded nodes change; used by v-model:loadedKeys.[loadedKeys: string[]]
loadEmitted when a node has loaded; loadedKeys is the set of loaded nodes.Type[loadedKeys: Set<string>, node: TreeNodeData]
selectEmitted when a node is selected or deselected, with its key, whether it is selected and its data. It shadows the native event of the same name on the root element.[selectedKey: string, selected: boolean, node: TreeNodeData]
searchEmitted when the search input changes; filteredExpandedKeys are the nodes expanded by the search or by modelValue and defaultValue, which controlled expansion can use to show the results.[input: string, filteredExpandedKeys: string[]]
contextMenuEmitted on a right click on a node.[event: MouseEvent, node: TreeNodeData]
doubleClickEmitted on a double click on a node.[event: MouseEvent, node: TreeNodeData]
dragStartEmitted when a node starts to be dragged.[info: TreeDragProps]
dragEnterEmitted when a drag enters a node; the payload holds expandedKeys when that expanded a collapsed node.[info: TreeDragEnterProps]
dragOverEmitted while a drag is over a node.[info: TreeDragProps]
dragLeaveEmitted when a drag leaves a node.[info: TreeDragProps]
dragEndEmitted when a drag ends.[info: TreeDragProps]
dropEmitted when a node is dropped; the payload holds dragNode, dragNodesKeys, dropPosition and dropToGap. It shadows the native event of the same name on the root element.[info: TreeOnDragProps]

Slots

SlotDescriptionScope parameters
labelContent of a node's label. Search still matches the label text in treeData.TreeLabelSlotProps
iconA node's icon, which can differ by node data; empty content keeps the directory icon.TreeIconSlotProps
expandIconThe expand icon. A single element gets the class and the click that expands; the parameters can also be bound by hand.TreeExpandIconSlotProps
fullLabelContent of the whole row. Tree provides the row element carrying the id, role and state, and virtualized positioning; the slot holds only what is inside.TreeFullLabelSlotProps
searchReplaces the search box; an empty slot hides it.TreeSearchSlotProps
emptyContentContent shown when there are no nodes, or when the search finds none with showFilteredOnly on; an empty slot hides the empty state.any
renderDraggingNodeThe element the browser shows while a node is dragged, with the node element nodeInstance and the node data node as parameters; on dragstart the slot content is rendered into a detached container and used as the drag image, so only app-level provides reach it.TreeDraggingNodeSlotProps

Exposes

NameDescriptionType
searchSearches as if the value were typed into the search box; filterTreeNode must be set.Type(value: string) => void
scrollToIn a virtualized tree, scrolls an expanded node into view; align is center by default.Type(scrollData: TreeScrollData) => void

The Vue Tree has no className property: undeclared attributes such as class, style and data-* land on the root element .semi-tree-wrapper, and aria-* attributes land on the role="tree" element. The empty state has no role="tree" element, so aria-* attributes are dropped there.

TreeNodeData

Key for TreeNodeData is required and must be unique, label can be duplicated. Value is not required; without it, the value property in the change event, modelValue, defaultValue and changeWithObject points to the key property. To ensure everything behave as expected, keep a consistency of whether to have value or not to have value.

PropertiesInstructionsTypeDefault
valueValuestring|number-
labelDisplayed label; custom content comes from the #label slotstring|number-
disabledDisabled. When the Tree's disabled is true the whole tree is disabled, and a node's disabled: false does not lift itbooleanfalse
keyRequired and must be uniquestring-
isLeafToggle whether node is leaf node, only takes effect in async load data with loadDataboolean-

Virtualize Object

itemSize is required to render virtualized tree。

PropertiesInstructionsTypeDefault
heightHeight. If passed as a string, computed height should not be 0 for render purpose, in other words, parent node should have offsetHeightnumber|string'100%'
itemSizeHeight for each line of treeNode, requirednumber-
widthWidthnumber|string'100%'

Methods

Tree exposes search and scrollTo through a template ref, typed as TreeHandle; see the Exposes part of the Tree table above.

<script setup lang="ts">
    import { useTemplateRef } from 'vue';
    import { Tree } from '@fibrous/semi-ui-vue';
    import type { TreeHandle } from '@fibrous/semi-ui-vue';

    const tree = useTemplateRef<TreeHandle>('tree');
    const search = (value: string) => tree.value?.search(value);
</script>

<template>
    <Tree ref="tree" filterTreeNode :treeData="treeData" />
</template>

Accessibility

ARIA

  • Tree supports passing in aria-label, aria-labelledby and aria-describedby to name and describe the Tree; they are all set on the role="tree" element;
  • Tree sets aria-level, aria-setsize, aria-posinset and aria-disabled on each node, and aria-expanded on nodes that can expand; a single-choice tree marks the selected node with aria-selected, a multi-choice tree marks checked nodes with aria-checked (mixed when half checked);
  • Tree will set role to tree and treeitem for corresponding parts.
  • The content of #fullLabel is wrapped in Tree's own li, which carries the id, role="treeitem" and the states above.
  • The expand arrow and the checkbox in front of a node only picture its state: they are hidden from screen readers and take no focus.

Keyboard Interaction

Tree follows the WAI-ARIA Tree View pattern: the whole tree is one Tab stop, focus stays on the tree, aria-activedescendant points at the current node, and the current node shows the focus ring.

  • ArrowDown, ArrowUp: move to the next or previous visible node; Home, End: move to the first or last visible node
  • ArrowRight: expand a collapsed node; on an expanded node, move to its first child (swapped with ArrowLeft in RTL)
  • ArrowLeft: collapse an expanded node; otherwise move to the parent
  • Enter, Space: select the current node, or check and uncheck it in a multi-choice tree; disabled nodes do not respond
  • Typing characters: move to the next node whose text starts with them

A node that is selected, checked, expanded, right-clicked or double-clicked with the pointer becomes the current node of the keyboard, and the arrow keys continue from it.

The keyboard does not trigger expandAction; the arrow keys expand and collapse.

Buttons and links inside a node (for example a control in the #label slot) can be reached with Tab. While one of them has focus, keys are handled by that control and the tree does not respond.

Demo:

<Tree aria-label="example tree" :treeData="treeData" />

Content Guidelines

  • Try to use phrases, capitalize the first letter
  • Keep the same form of terminology between levels, such as all place names or country names

Design Tokens

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$transition_duration-tree_option-bgvar(--semi-transition_duration-none)树选项-背景色-动画持续时间
$transition_function-tree_option-bgvar(--semi-transition_function-easeIn)树选项-背景色-过渡曲线
$transition_delay-tree_option-bgvar(--semi-transition_delay-none)树选项-背景色-延迟时间
$transform_scale-tree-optionvar(--semi-transform_scale-none)树选项-放大
$color-tree_option-text-defaultvar(--semi-color-text-0)树选项文字颜色
$color-tree_option-icon-defaultvar(--semi-color-text-2)树选项图标颜色
$color-tree_option-bg-hovervar(--semi-color-fill-0)树选项背景色 - 悬停
$color-tree_option_selected-bg-defaultvar(--semi-color-fill-1)树选项背景色 - 按下
$color-tree_option-bg-activevar(--semi-color-primary-light-default)树选项背景色 - 选中
$color-tree_option_disabled-text-defaultvar(--semi-color-disabled-text)禁用树选项文字颜色
$color-tree_option-icon-hovervar(--semi-color-text-0)树选项图标颜色 - 悬浮
$color-tree_option-icon-activevar(--semi-color-black)树选项图标颜色 - 按下
$color-tree_option_loading-icon-defaultvar(--semi-color-primary)树选项加载 spin 颜色
$color-tree_option_draggable_insert-border-defaultvar(--semi-color-primary)树选项加载 spin 颜色
$font-tree_option_hightlight-fontWeight$font-weight-bold树选项高亮文本字重
$color-tree_option_hightlight-textvar(--semi-color-primary)树选项高亮文本颜色
$radius-tree_checkbox_addonvar(--semi-border-radius-small)多选树 checkbox 圆角
$spacing-tree_search_wrapper-paddingX12px树搜索框水平内边距
$spacing-tree_search_wrapper-paddingY8px树搜索框垂直内边距
$spacing-tree_option_level-paddingLeft20px树选项缩进增量
$spacing-tree_option_level1-paddingLeft8px树选项最上层左侧内边距
$spacing-tree_optionList-paddingX0树选项列表水平内边距
$spacing-tree_optionList-paddingY8px树选项列表垂直内边距
$spacing-tree_option-paddingTop4px树选项顶部内边距
$spacing-tree_option-paddingBottom4px树选项底部内边距
$spacing-tree_icon-marginRight8px树选项图标右侧外边距
$spacing-tree_label_withIcon-marginRight8px树选项图标右侧外边距
$spacing-tree_option_draggable-paddingY2px可拖拽的树选项垂直内边距
$spacing-tree_option_draggable-paddingX0可拖拽的树选项水平内边距
$width-tree_emptyIcon$width-icon-small树选项空图标宽度
$width-tree_spinIcon$width-icon-small树选项加载 spin 宽度
$width-tree_option_draggable-border2px可拖拽的树标示线宽度
$width-tree_option_line1pxshowline展示线宽度