Input · Cascader

Cascader

Used to select an option under a multi-level classification.

Usage scenarios

Differences from TreeSelect component:

  • TreeSelect: The core value lies in target node. The structure is to facilitate users to quickly filter out target options. The final node is what the user wants. It is commonly used in file/folder selection, organizational structure, permission allocation and other scenarios.
  • Cascader: The core selection value lies in the path. What the user selects is not an isolated point, but a complete path from root to leaf, which is often used in scenarios such as geographical location and product classification.

Demos

How to import

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

Basic Usage

Basic usage, only leaf nodes can be selected by default.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <Cascader placeholder="请选择所在地区" :style="style" :treeData="treeData" />
</template>

Multiple

Set multiple to make multiple selections.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader :defaultValue="['zhejiang', 'ningbo', 'jiangbei']" multiple placeholder="请选择所在地区" style="width: 300px" :treeData="treeData" />
</template>

Searchable

Use filterTreeNode to support search input.

By default, the label value is searched (using the includes method of the string for matching, case-insensitive). You can specify other attribute values through treeNodeFilterProp to search. The label is text only, and search still matches the label text when you customize the node content with the #label slot. To match other text, store it in another field in treeData and specify the field through treeNodeFilterProp.

The default search results will only display the paths of leaf nodes. If you want to display more results, you can set filterLeafOnly to false.





filterLeafOnly=false:


Label 为自定义节点,指定其他属性进行搜索
Code
<script setup lang="ts">
    import { IconMapPinStroked } from '@fibrous/semi-icons-vue';
    import { Cascader, Typography } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];

    // The label slot adds a decorative icon in the columns; labelText holds the text that search matches
    const labelNodeTreeData = [
        {
            label: '浙江省',
            labelText: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    labelText: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', labelText: '西湖区', value: 'xihu' },
                        { label: '萧山区', labelText: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', labelText: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    labelText: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', labelText: '海曙区', value: 'haishu' },
                        { label: '江北区', labelText: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <div>
        <Cascader filterTreeNode placeholder="默认对label值进行搜索" style="width: 300px" :treeData="treeData" />
        <br />
        <br />
        <Cascader filterTreeNode placeholder="对value值进行搜索" style="width: 300px" :treeData="treeData" treeNodeFilterProp="value" />
        <br />
        <br />
        <Typography.Title :heading="6">filterLeafOnly=false:</Typography.Title>
        <Cascader :filterLeafOnly="false" filterTreeNode placeholder="filterLeafOnly=false" style="width: 300px" :treeData="treeData" />
        <br />
        <br />
        <Typography.Title :heading="6">Label 为自定义节点,指定其他属性进行搜索</Typography.Title>
        <Cascader displayProp="labelText" filterTreeNode placeholder="Search for labelText" style="width: 300px" :treeData="labelNodeTreeData" treeNodeFilterProp="labelText">
            <template #label="{ item }">
                <span :style="{ display: 'inline-flex', alignItems: 'center', columnGap: '4px' }">
                    <IconMapPinStroked />
                    {{ item.label }}
                </span>
            </template>
        </Cascader>
    </div>
</template>

Searchable Multiple Selection

When multiple selection and search are supported at the same time, in this scenario, you can delete the corresponding selected item by pressing the BackSpace key.

海曙区
Code
<script setup lang="ts">
    import { ref } from 'vue';
    import { Cascader, type CascaderValue } from '@fibrous/semi-ui-vue';

    const value = ref<CascaderValue>(['zhejiang', 'ningbo', 'haishu']);

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader v-model="value" filterTreeNode multiple placeholder="请选择所在地区" style="width: 300px" :treeData="treeData" />
</template>

Filtered data can be sorted using filterSorter.

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

    const treeData = [
        {
            label: 'Product',
            value: 'Product',
            children: [
                { label: 'Semi-Material', value: 'Semi-Material' },
                { label: 'Semi-DSM', value: 'Semi-DSM' },
                { label: 'Semi', value: 'Semi' },
                { label: 'Semi-C2D', value: 'Semi-C2D' },
                { label: 'Semi-D2C', value: 'Semi-D2C' },
            ],
        },
    ];

    // The sorter receives the two result paths, so compare their last nodes
    const filterSorter: CascaderProps['filterSorter'] = (first, second, inputValue) => {
        const firstData = first[first.length - 1];
        const lastData = second[second.length - 1];
        if (firstData.label === inputValue) {
            return -1;
        } else if (lastData.label === inputValue) {
            return 1;
        } else {
            return firstData.label < lastData.label ? -1 : 1;
        }
    };
</script>

<template>
    <div>
        <Cascader :filterSorter="filterSorter" filterTreeNode placeholder="输入 s 查看排序效果" :style="{ width: '300px' }" :treeData="treeData" />
    </div>
</template>

If you want to customize the rendering options after the search, you can use the #filterItem slot to achieve custom rendering of the entire line. The slot props have the type CascaderFilterItemSlotProps:

interface CascaderFilterItemSlotProps {
    class: ClassValue; // Class of the option, bind it to the root element of the slot
    inputValue: string; // Search bar search content
    disabled: boolean; // Whether to disable
    data: CascaderData[]; // Search result data, the nodes on this path
    selected: boolean; // Selected state when single selection
    checkStatus: {
        // Checked state when multiple selection
        checked: boolean;
        halfChecked: boolean;
    };
    onClick: (event: MouseEvent) => void; // Select the option in single selection
    onCheck: (event: CheckboxEvent) => void; // Check the option in multiple selection, bind it to the change event of Checkbox
}

When the slot renders a single element root, that element automatically gets the id, role="option" and the selected state of the option, plus the positioning style of the row when virtualizeInSearch is on; when it renders text or several nodes, the component wraps them in an li. The slot root should be a plain element.

The example is as follows

鼠标 hover 到选项可查看被省略文本完整内容



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

    const treeData = [
        {
            label: 'Semi',
            value: 'Semi',
            children: [
                {
                    label: 'Semi-Material Semi-Material Semi-Material Semi-Material',
                    value: 'Semi-Material',
                },
                {
                    label: 'Semi-DSM Semi-DSM Semi-DSM Semi-DSM',
                    value: 'Semi-DSM',
                },
                {
                    label: 'Semi Design Semi Design Semi Design Semi Design',
                    value: 'Semi',
                },
                {
                    label: 'Semi-C2D Semi-C2D Semi-C2D Semi-C2D Semi-C2D',
                    value: 'Semi-C2D',
                },
                {
                    label: 'Semi-D2C Semi-D2C Semi-D2C Semi-D2C Semi-D2C ',
                    value: 'Semi-D2C',
                },
            ],
        },
    ];
    const ellipsis: Ellipsis = { showTooltip: { opts: { style: { wordBreak: 'break-all' } } } };
    const pathLabel = (data: CascaderData[]) => data.map((item) => item.label).join(' / ');
</script>

<template>
    <div>
        <p>鼠标 hover 到选项可查看被省略文本完整内容</p>
        <br />
        <Cascader filterTreeNode placeholder="单选,输入 s 自定义搜索选项渲染结果" :style="{ width: '320px' }" :treeData="treeData">
            <template #filterItem="{ class: className, data, onClick, selected }">
                <li :aria-selected="selected" :class="className" role="option" :style="{ justifyContent: 'flex-start' }" @click="onClick">
                    <TypographyText :ellipsis="ellipsis" :style="{ width: '270px', color: selected ? 'var(--semi-color-primary)' : undefined }">
                        {{ pathLabel(data) }}
                    </TypographyText>
                </li>
            </template>
        </Cascader>
        <br />
        <Cascader filterTreeNode multiple placeholder="多选,输入 s 自定义搜索选项渲染结果" :style="{ width: '320px', marginTop: '20px' }" :treeData="treeData">
            <template #filterItem="{ class: className, data, checkStatus, onCheck }">
                <li :aria-label="pathLabel(data)" :aria-selected="checkStatus.checked" :class="className" role="option" :style="{ justifyContent: 'flex-start' }">
                    <Checkbox :indeterminate="checkStatus.halfChecked" :modelValue="checkStatus.checked" :tabIndex="-1" @change="onCheck">
                        <TypographyText :ellipsis="ellipsis" :style="{ width: '250px' }">
                            {{ pathLabel(data) }}
                        </TypographyText>
                    </Checkbox>
                </li>
            </template>
        </Cascader>
    </div>
</template>

If there are a lot of options in the search results, you can optimize performance by setting virtualizeInSearch to enable the virtualization of the search result panel. virtualizeInSearch is an object containing the following values:

  • height: Option list height in px (number), default 180, the same as the dropdown height
  • width: Option list width value
  • itemSize: The height of each row of Option

Limit Tags Displayed

When multiple selections, you can use maxTagCount to limit the number of tags displayed, and the excess will be displayed as +N.

You can use showRestTagsPopover to set whether hover +N displays Popover after maxTagCount is exceeded, the default is false. And, you can also configure Popover in the restTagsPopoverProps property.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
    const restTagsPopoverProps = { position: 'top' } as const;
    const defaultValue = [
        ['zhejiang', 'ningbo', 'haishu'],
        ['zhejiang', 'hangzhou', 'xihu'],
    ];
</script>

<template>
    <Cascader :defaultValue="defaultValue" :maxTagCount="1" multiple placeholder="请选择所在地区" :restTagsPopoverProps="restTagsPopoverProps" showRestTagsPopover :style="{ width: '300px' }" :treeData="treeData" />
</template>

Limit Tags Number

In a multi-selection scene, use max to limit the number of multi-selection selections. After max is exceeded, the exceed event will be emitted.

Code
<script setup lang="ts">
    import { Cascader, Toast, type CascaderEntity } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
    const defaultValue = [['zhejiang', 'ningbo', 'haishu']];

    const onExceed = (v: CascaderEntity[]) => {
        Toast.warning('exceed max');
        console.log(v);
    };
</script>

<template>
    <Cascader :defaultValue="defaultValue" :max="1" multiple placeholder="请选择所在地区" :style="{ width: '300px' }" :treeData="treeData" @exceed="onExceed" />
</template>

Change on Select

In the case of single selection, you can also set changeOnSelect to allow the parent option to be selected.



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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <div>
        <Cascader changeOnSelect placeholder="选择即改变" :style="style" :treeData="treeData" />
        <br />
        <br />
        <Cascader changeOnSelect filterTreeNode placeholder="可搜索的选择即改变" :style="style" :treeData="treeData" />
    </div>
</template>

Custom Display Property

Set displayProp to select which property in the data you would like to display. By default, label is displayed.

单选


多选
Code
<script setup lang="ts">
    import { Cascader, TypographyTitle } from '@fibrous/semi-ui-vue';

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const defaultValue = ['zhejiang', 'ningbo', 'jiangbei'];
    const style = { width: '300px' };
</script>

<template>
    <TypographyTitle :heading="6">单选</TypographyTitle>
    <Cascader :defaultValue="defaultValue" displayProp="value" placeholder="回填时显示数据的value值" :style="style" :treeData="treeData" />
    <br />
    <br />
    <TypographyTitle :heading="6">多选</TypographyTitle>
    <Cascader :defaultValue="defaultValue" displayProp="value" multiple placeholder="回填时显示数据的value值" :style="style" :treeData="treeData" />
</template>

displayRender is split into two slots.

When single selection (multiple is false), use #display="{ labels, path }" to set the displayed content, where labels holds the displayProp of each node on the path and path holds the nodes on the path (the raw values when the value is not in treeData, as in the payload of change with changeWithObject); empty slot content shows the placeholder.

When multiple selection (multiple is true), use #tag="{ entity, index, disabled, onClose }" to replace each tag, where entity is the relevant data of the node and onClose unchecks the node; bind it to your own Tag when you need a close button. It works in searchable multiple selection too. The type of entity is CascaderEntity, as follows:

interface Entity {
    children?: Entity[]; // children list
    data: CascaderData; // treedata
    ind: number; // index
    key: string; // key
    level: number; // node level
    parent?: Entity; // parent data
    parentKey?: string; // parent key
    path: string[]; // key path
    pos: string; // position of the node in treeData, such as '0-0-1'
    valuePath: string[]; // value path
}

The label of a data item is text only. To render custom content in an option of the columns, use the #label="{ item }" slot, where item is the data of the node; the search results, the selection and the tags still show the text of the node.

单选


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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
    const defaultValue = ['zhejiang', 'ningbo', 'jiangbei'];
</script>

<template>
    <TypographyTitle :heading="6">单选</TypographyTitle>
    <Cascader :defaultValue="defaultValue" placeholder="自定义回填时显示数据的格式" :style="{ width: '300px' }" :treeData="treeData">
        <template #display="{ labels }">已选择:{{ labels.join(' -> ') }}</template>
    </Cascader>
    <br />
    <br />
    <TypographyTitle :heading="6">多选</TypographyTitle>
    <Cascader :defaultValue="defaultValue" multiple placeholder="自定义回填时显示数据的格式" :style="{ width: '300px' }" :treeData="treeData">
        <template #tag="{ entity }">
            <Tag color="white" :style="{ marginRight: '4px' }">{{ entity.data.label }}</Tag>
        </template>
    </Cascader>
</template>

Custom Separator

You can use separator to set the separator, including: the separator of the content displayed in the dropdown during search and displayed in the Trigger during single selection.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader :defaultValue="['zhejiang', 'ningbo', 'jiangbei']" filterTreeNode separator=" > " style="width: 300px" :treeData="treeData" />
</template>

Disabled



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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
            ],
        },
    ];
    const defaultValue = ['zhejiang', 'hangzhou', 'xihu'];
    const style = { width: '300px' };
</script>

<template>
    <div>
        <Cascader disabled placeholder="请选择所在地区" :style="style" :treeData="treeData" />
        <br />
        <br />
        <Cascader :defaultValue="defaultValue" disabled filterTreeNode placeholder="请选择所在地区" :style="style" :treeData="treeData" />
    </div>
</template>

Disable Strictly

You can use disableStrictly 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.

Take the following demo as an example, the node "宁波市" is strictly disabled. Therefore, when we change the selected state of its parent node "浙江省", it will not affect the selected state of the node "宁波市".

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    disabled: true,
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <Cascader disableStrictly multiple placeholder="请选择所在地区" :style="style" :treeData="treeData" />
</template>

the Way of Expand Menu

You can use showNext to set the time to expand the Dropdown submenu, optional: click (default), hover.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader placeholder="请选择所在地区" showNext="hover" style="width: 300px" :treeData="treeData" />
</template>

Click to Select

In multiple mode, clicking on non-leaf nodes does not trigger selection by default. You can use clickToSelect to enable selecting any node on click.

This API is especially useful when combined with showNext="hover": hovering expands the submenu, while clicking selects the current node.

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

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <Cascader clickToSelect multiple placeholder="请选择所在地区" showNext="hover" :style="style" :treeData="treeData" />
</template>

Additional items

We have reserved slots at the top and bottom of the cascade selector. You can set them through the #topSlot or #bottomSlot slot.

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

    const { Text } = Typography;

    const slotStyle = {
        height: '36px',
        display: 'flex',
        padding: '0 32px',
        alignItems: 'center',
        cursor: 'pointer',
        borderTop: '1px solid var(--semi-color-border)',
    };

    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader placeholder="请选择所在地区" style="width: 300px" :treeData="treeData">
        <template #bottomSlot>
            <div :style="slotStyle">
                <Text>找不到相关选项?</Text>
                <Text link>去新建</Text>
            </div>
        </template>
    </Cascader>
</template>

Controlled Component

Bind modelValue with v-model to use Cascader as a controlled component; the change event is still emitted. modelValue must be replaced as a whole, changes made in place to a controlled array are not noticed; passing undefined goes back to uncontrolled and clears the selection.

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

    const value = ref<CascaderSimpleValue[]>([]);
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <Cascader v-model="value" placeholder="请选择所在地区" :style="style" :treeData="treeData" />
</template>

Auto Merge Value

In the multi-selection (multiple=true) scenario, when we select the ancestor node, if we want the value not to include its corresponding descendant nodes, we can set it by autoMergeValue, and the default is true. When autoMergeValue and leafOnly are turned on at the same time, the latter has a higher priority.

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

    const value = ref<CascaderSimpleValue[][]>([]);
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        {
                            label: '西湖区',
                            value: 'xihu',
                        },
                        {
                            label: '萧山区',
                            value: 'xiaoshan',
                        },
                        {
                            label: '临安区',
                            value: 'linan',
                        },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        {
                            label: '海曙区',
                            value: 'haishu',
                        },
                        {
                            label: '江北区',
                            value: 'jiangbei',
                        },
                    ],
                },
            ],
        },
    ];
    const style = { width: '300px' };
</script>

<template>
    <Cascader v-model="value" :autoMergeValue="false" multiple placeholder="autoMergeValue 为 false" :style="style" :treeData="treeData" />
</template>

Leaf Only

In multiple selection, you can set the value to include only leaf nodes by turning on leafOnly, that is, the displayed Tag and the value of v-model and the change event only include leaf nodes.

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

    const value = ref<CascaderSimpleValue[][]>([]);
    const onChange = (v: CascaderSimpleValue[][]) => {
        console.log(v);
    };
    const treeData = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader v-model="value" leafOnly multiple placeholder="开启 leafOnly" :style="{ width: '300px' }" :treeData="treeData" @change="onChange" />
</template>

Checked RelationShip

In multiple, 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.

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

    const treeData = [
        {
            label: '亚洲',
            value: 'Asia',
            children: [
                {
                    label: '中国',
                    value: 'China',
                    children: [
                        {
                            label: '北京',
                            value: 'Beijing',
                        },
                        {
                            label: '上海',
                            value: 'Shanghai',
                        },
                    ],
                },
            ],
        },
        {
            label: '北美洲',
            value: 'North America',
        },
    ];
    const defaultValue = [['Asia'], ['Asia', 'China', 'Beijing']];
    const style = { width: '300px' };
</script>

<template>
    <Cascader checkRelation="unRelated" :defaultValue="defaultValue" multiple :style="style" :treeData="treeData" />
</template>

Dynamic Update of Data

treeData can be replaced as a whole or changed in place in reactive data (such as push, or assigning children); both are noticed and recalculated, and a new array with equal content keeps the expanded and selected state.



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

    const treeData = ref<CascaderData[]>([]);

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

<template>
    <Cascader placeholder="请选择" :style="{ width: '300px' }" :treeData="treeData" />
    <br />
    <br />
    <Button @click="add">动态改变数据</Button>
</template>

Load Async Data

You could use loadData to load data asynchronously.

Could not be used together with searching

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

    const data = ref<CascaderData[]>([
        { label: 'Node1', value: '0-0' },
        { label: 'Node2', value: '0-1' },
        { label: 'Node3', value: '0-2', isLeaf: true },
    ]);

    const updateTreeData = (list: CascaderData[], value: CascaderData['value'], children: CascaderData[]): CascaderData[] =>
        list.map((node) => {
            if (node.value === value) {
                return { ...node, children };
            }
            if (node.children) {
                return { ...node, children: updateTreeData(node.children, value, children) };
            }
            return node;
        });

    const onLoadData = (selectedOpt: CascaderData[]) => {
        const targetOpt = selectedOpt[selectedOpt.length - 1];
        const { label, value } = targetOpt;
        return new Promise<void>((resolve) => {
            if (targetOpt.children) {
                resolve();
                return;
            }

            setTimeout(() => {
                data.value = updateTreeData(data.value, value, [
                    {
                        label: `${label} - 1`,
                        value: `${label}-1`,
                        isLeaf: selectedOpt.length > 1,
                    },
                    {
                        label: `${label} - 2`,
                        value: `${label}-2`,
                        isLeaf: selectedOpt.length > 1,
                    },
                ]);
                resolve();
            }, 1000);
        });
    };
</script>

<template>
    <Cascader :loadData="onLoadData" placeholder="Please select" :style="{ width: '300px' }" :treeData="data" />
</template>

v>=2.97.0 Cascader supports remote search. With remote enabled, search input no longer goes through local filtering; only the search event is emitted, and you are responsible for fetching treeData asynchronously based on the input. This mirrors Select's remote behavior.

Deep & long list

When your data structure level is particularly deep, the Cascader drop-down menu may be at the top of the screen. At this time, we recommend setting overflow -x: auto and a suitable width for the drop-down menu (it is recommended to use a width of N+0.5 columns, the most Expand to display half a column to give users a visual cue that they can scroll in the horizontal direction)

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

    const treeData = [
        {
            label: 'A',
            value: 'A',
            children: [
                {
                    label: 'B',
                    value: 'B',
                    children: [
                        {
                            label: 'C',
                            value: 'C',
                            children: [
                                {
                                    label: 'D',
                                    value: 'D',
                                    children: [
                                        {
                                            label: 'E',
                                            value: 'E',
                                            children: [{ label: 'F', value: 'F' }],
                                        },
                                    ],
                                },
                            ],
                        },
                    ],
                },
            ],
        },
    ];
</script>

<template>
    <Cascader dropdownClassName="components-cascader-demo" placeholder="请选择所在地区" :style="{ width: '300px' }" :treeData="treeData" />
</template>
.components-cascader-demo {
    .semi-cascader-option-lists {
        max-width: 510px;
        overflow-x: auto;
    }
}

Custom Trigger

If the default trigger style cannot meet your needs, you can use the #trigger slot to customize the display of the select box

The slot props of #trigger are as follows, with the type CascaderTriggerSlotProps

interface CascaderTriggerSlotProps {
    /* All props passed to Cascader by users */
    componentProps: CascaderProps;
    /* Whether to disable Cascader */
    disabled: boolean;
    /**
     * The hierarchical position of the selected node in treeData,
     *  as in the following example, when "浙江省-杭州市-萧山区" is
     *  selected, the value here is '0-0-1'; a Set<string> in multiple mode
     */
    value: string | undefined | Set<string>;
    /* The search text while open; while a searchable single Cascader is closed, the display text of the selected path (the texts joined by separator) */
    inputValue: string;
    /**
     * The function used to update the value of the input box. You
     *  should call this function when the value of the Input component
     *  customized by #trigger is updated to synchronize the
     *  state with Cascader, you need to set the filterTreeNode parameter
     *  to non-false when use it
     */
    onSearch: (inputValue: string) => void;
    /* Function to clear the value; pass the click event when it is bound to a control inside the trigger, so the click does not open the dropdown */
    onClear: (event?: MouseEvent) => void;
    /* Placeholder of Cascader */
    placeholder?: string;
    /* Used to delete a single item, the input parameter is the hierarchical
     * position of the node in treeData (the same format as value)
     */
    onRemove: (pos: string) => void;
}

Code
<script setup lang="ts">
    import { IconChevronDown, IconClose } from '@fibrous/semi-icons-vue';
    import { Button, ButtonGroup, Cascader, Tag, TagInput, type CascaderData } from '@fibrous/semi-ui-vue';

    const treeData: CascaderData[] = [
        {
            label: '浙江省',
            value: 'zhejiang',
            children: [
                {
                    label: '杭州市',
                    value: 'hangzhou',
                    children: [
                        { label: '西湖区', value: 'xihu' },
                        { label: '萧山区', value: 'xiaoshan' },
                        { label: '临安区', value: 'linan' },
                    ],
                },
                {
                    label: '宁波市',
                    value: 'ningbo',
                    children: [
                        { label: '海曙区', value: 'haishu' },
                        { label: '江北区', value: 'jiangbei' },
                    ],
                },
            ],
        },
    ];

    const getLabelFromValue = (value: string) => {
        const [first, ...rest] = value.split('-').map((item) => Number(item));
        let node: CascaderData | undefined = treeData[first];
        rest.forEach((item) => {
            node = node?.children?.[item];
        });
        return node?.label;
    };
</script>

<template>
    <Cascader placeholder="Custom Trigger" :treeData="treeData">
        <template #trigger="{ value, placeholder, onClear }">
            <!-- The clear button is a sibling of the trigger button: buttons cannot nest -->
            <ButtonGroup theme="light">
                <Button iconPosition="right">
                    <template #icon>
                        <IconChevronDown />
                    </template>
                    {{ value ? getLabelFromValue(value) : placeholder }}
                </Button>
                <Button v-if="value" aria-label="清除" @click.stop="onClear($event)">
                    <template #icon>
                        <IconClose />
                    </template>
                </Button>
            </ButtonGroup>
        </template>
    </Cascader>
    <br />
    <Cascader filterTreeNode multiple placeholder="Custom Trigger" style="width: 300px" :treeData="treeData">
        <template #trigger="{ value, onSearch, onRemove }">
            <TagInput :modelValue="Array.from(value)" @inputChange="onSearch">
                <template #tag="{ value: pos }">
                    <Tag closable :style="{ marginLeft: '2px' }" :tagKey="pos" @close="onRemove(pos)">
                        {{ getLabelFromValue(pos) }}
                    </Tag>
                </template>
            </TagInput>
        </template>
    </Cascader>
</template>

API reference

Cascader

Props

PropertyDescriptionTypeDefault
modelValueThe selected entry, bound with v-model. Passing it makes the component controlled, and undefined goes back to uncontrolled and clears the selection. Only a replaced value is noticed, changes made in place to a controlled array are not. In single mode it is a node or a path, in multiple mode it can also be an array of paths.TypeByMultiple<Multiple, CascaderSimpleValue | CascaderSimpleValue[], CascaderValue>undefined
defaultValueThe initially selected entry, read once on mount.TypeByMultiple<Multiple, CascaderSimpleValue | CascaderSimpleValue[], CascaderValue>semiGlobalDefault("Cascader", "defaultValue")
multipleWhether several paths can be selected. The types of modelValue, defaultValue, change and select follow it; it has no global default.Multipleundefined
treeDataNode data. The label is text only, use the label slot for node content. Changes made in place to reactive data are noticed, and a new array with equal content keeps the expanded and selected state.CascaderData[]semiDefault("Cascader", "treeData", [])
keyMapsCustom field names for value, label, children, disabled and isLeaf in a node.KeyMapPropssemiGlobalDefault("Cascader", "keyMaps")
changeOnSelectWhether a non-leaf node can be selected in single mode.booleansemiDefault("Cascader", "changeOnSelect", false)
autoMergeValueIn multiple mode, the value leaves out the descendants of a selected parent. Changes after mount are not followed; leafOnly takes precedence when both are on.booleansemiDefault("Cascader", "autoMergeValue", true)
leafOnlyIn multiple mode, the value and the tags shown only include leaf nodes. Changes after mount are not followed.booleansemiDefault("Cascader", "leafOnly", false)
checkRelationIn multiple mode, how the checked states of the nodes relate: related links parents and children, unRelated keeps them independent."related" | "unRelated"semiDefault("Cascader", "checkRelation", strings.RELATED)
disableStrictlyWhether to disable strictly. A disabled node then does not change when its parent or children are checked or unchecked.booleansemiDefault("Cascader", "disableStrictly", false)
maxIn multiple mode, the most paths that can be selected; exceeding it emits the exceed event.numbersemiGlobalDefault("Cascader", "max")
maxTagCountIn multiple mode, the most tags shown; the rest appear as +N.numbersemiGlobalDefault("Cascader", "maxTagCount")
showRestTagsPopoverWhether hovering +N shows the remaining tags in a Popover once maxTagCount is exceeded.booleansemiDefault("Cascader", "showRestTagsPopover", false)
restTagsPopoverPropsConfiguration of the Popover of the remaining tags, such as position and trigger. See Popover.PopoverPropssemiGlobalDefault("Cascader", "restTagsPopoverProps")
enableLeafClickIn multiple mode, whether clicking the option of a leaf node checks it.booleansemiDefault("Cascader", "enableLeafClick", false)
clickToSelectIn multiple mode, clicking any node selects it. Often used with showNext set to hover: hovering expands the submenu and clicking selects the node.booleansemiDefault("Cascader", "clickToSelect", false)
showNextWhen the submenu expands: on click or on hover."hover" | "click"semiDefault("Cascader", "showNext", strings.SHOW_NEXT_BY_CLICK)
loadDataLoads data asynchronously. It receives the path from the root to the current node and must return a Promise; after a rejection the node leaves the loading state and can be expanded again. Cannot be used with search. It remains a function prop because the returned Promise is read.Type((selectOptions: CascaderData[]) => Promise<void>)undefined
filterTreeNodeWhether search is on. True matches the input against the field named by treeNodeFilterProp, case-insensitively; a custom match function can be given instead. It remains a function prop because its result is read.Typeboolean | ((inputValue: string, treeNodeString: string, data?: CascaderData | undefined) => boolean)undefined
filterSorterSorts the filtered results; it receives the two result paths (the node arrays from the root to the matched node) and the input. It remains a function prop because its result is read.Type((first: CascaderData[], second: CascaderData[], inputValue: string) => number)undefined
filterLeafOnlyWhether the search results only show the paths of leaf nodes.booleansemiDefault("Cascader", "filterLeafOnly", true)
treeNodeFilterPropThe node field matched against the search input, label by default.stringsemiDefault("Cascader", "treeNodeFilterProp", "label")
remoteWhether to search remotely. Input is then not filtered locally and only emits the search event, and the caller updates treeData asynchronously.booleansemiDefault("Cascader", "remote", false)
displayPropThe node field shown in the trigger and the tags, label by default.stringsemiDefault("Cascader", "displayProp", "label")
separatorThe separator of a path in the search results and in the trigger in single mode.stringsemiDefault("Cascader", "separator", " / ")
changeWithObjectWhether the change event carries the data of each node instead of its value. A controlled modelValue and defaultValue must then be node data too, with a value. This is a boolean attribute, not an event: do not write it as @changeWithObject.booleansemiDefault("Cascader", "changeWithObject", false)
placeholderPlaceholder of the select box.stringsemiGlobalDefault("Cascader", "placeholder")
searchPlaceholderPlaceholder of the search input; placeholder is used when it is not given.stringsemiGlobalDefault("Cascader", "searchPlaceholder")
searchPositionWhere the search input is: trigger puts it in the trigger, custom leaves it to the caller, who passes the keyword with the search method."trigger" | "custom"TypesemiDefault("Cascader", "searchPosition", strings.SEARCH_POSITION_TRIGGER)
disabledWhether the Cascader is disabled. A disabled Cascader cannot be reached with Tab.booleansemiDefault("Cascader", "disabled", false)
sizeSize of the select box.SizesemiDefault("Cascader", "size", "default")
borderlessWhether to use the borderless style.booleansemiDefault("Cascader", "borderless", false)
validateStatusValidation status of the trigger; it only affects the style."default" | "error" | "warning" | "success"semiGlobalDefault("Cascader", "validateStatus")
insetLabelIdThe id of the insetLabel content, used to name the combobox; the component generates one when the insetLabel slot is used and this is not given.stringundefined
showClearWhether to show the clear button.booleansemiDefault("Cascader", "showClear", false)
defaultOpenWhether the dropdown is open on mount.booleansemiDefault("Cascader", "defaultOpen", false)
motionWhether the dropdown animates.booleansemiDefault("Cascader", "motion", true)
autoAdjustOverflowWhether the dropdown flips its direction when an edge covers it.booleansemiGlobalDefault("Cascader", "autoAdjustOverflow")
positionDirection of the dropdown, bottomLeft by default and bottomRight in RTL.Type"top" | "bottom" | "left" | "right" | "topLeft" | "topRight" | "leftTop" | "leftBottom" | "rightTop" | "rightBottom" | "bottomLeft" | "bottomRight" | "leftTopOver" | "rightTopOver" | "leftBottomOver" | "rightBottomOver"semiGlobalDefault("Cascader", "position")
stopPropagationWhether clicks on the dropdown stop bubbling.string | booleansemiDefault("Cascader", "stopPropagation", true)
dropdownClassNameClass of the dropdown.stringsemiGlobalDefault("Cascader", "dropdownClassName")
dropdownStyleStyle of the dropdown.StyleValuesemiGlobalDefault("Cascader", "dropdownStyle")
preventScrollWhether focusing inside the component stops the browser scrolling the document to show the focused element.booleansemiGlobalDefault("Cascader", "preventScroll")
virtualizeInSearchVirtual list configuration of the search results, made of height, width and itemSize, for large result sets; height defaults to 180, the same as the dropdown height.VirtualizesemiGlobalDefault("Cascader", "virtualizeInSearch")

Events

EventDescriptionCallback parameters
update:modelValueEmitted when the selection changes; the update event of v-model, with the same payload as change.Type[value: ByMultiple<Multiple, CascaderSimpleValue[], CascaderSimpleValue[][]>]
changeEmitted when the selection changes. In single mode the selected path, in multiple mode an array of the selected paths; node data with changeWithObject. It shares its name with the native change and hides the native one that bubbles from inside the root.Type[value: ByMultiple<Multiple, CascaderSimpleValue[], CascaderSimpleValue[][]>]
selectEmitted when a node is selected, with the value of the node. In multiple mode, one checked node gives its value and more give an array.Type[value: ByMultiple<Multiple, string | number, string | number | (string | number)[]>]
searchEmitted when the search input changes, with the text.[value: string]
focusEmitted when a click or a key opens the dropdown, with that MouseEvent or KeyboardEvent. It is not a DOM focus event and Tabbing in or out does not emit it.[event: MouseEvent | KeyboardEvent]
blurEmitted when the dropdown closes: a MouseEvent for a click outside, a KeyboardEvent for Esc, undefined when the close method closes it. It is not a DOM blur event.[event?: MouseEvent | KeyboardEvent | undefined]
dropdownVisibleChangeEmitted when the dropdown opens or closes, with whether it is open.[visible: boolean]
clearEmitted when the clear button clears the selection.[]
exceedEmitted in multiple mode when the selection goes past max, with the Entity array of the checked nodes.[checkedItem: CascaderEntity[]]
loadEmitted when a node has loaded, with the set of loaded keys and the node data.Type[newLoadedKeys: Set<string>, data: CascaderData]
listScrollEmitted when a column of the tree scrolls, with the scroll event and the column. Scrolling the search results does not emit it.[event: Event, panel: CascaderScrollPanelProps]
keydownEmitted once per key press before the Cascader handles it; calling preventDefault there does not stop the Cascader. Except for a key on the clear button or a control inside a custom trigger, it is emitted in the capture phase on the trigger, so stopPropagation there keeps the key from the search input and skips the trigger's handling of it (opening the dropdown, closing it on Esc); the options still take it. It shares its name with the native keydown and hides the native one that bubbles from inside the root.[event: KeyboardEvent]

Slots

SlotDescriptionScope parameters
labelContent of an option in the columns; the search results, the selection and the tags still show the text of the node.CascaderLabelSlotProps
displayThe selected path shown in single mode, with the displayProp of each node and the nodes; path holds the raw values when the value is not in treeData. Empty content shows the placeholder.CascaderDisplaySlotProps
tagReplaces each tag in multiple mode; onClose unchecks the node.CascaderTagSlotProps
filterItemReplaces a row of the search results. A single element root gets the id, role and state of the option, otherwise the component wraps it in an li.CascaderFilterItemSlotProps
triggerReplaces the content of the trigger.TypeCascaderTriggerSlotProps<Multiple>
expandIconCustom expand icon; empty content keeps the default icon.any
arrowIconCustom dropdown arrow on the right; an empty slot hides the arrow. Hovering shows the clear button first when it is visible.any
clearIconCustom icon of the clear button, effective when showClear is true; empty content keeps the default icon.any
emptyContentContent shown when there is no data to pick or the search has no result. The default text from the locale shows when it is not given, and an empty slot hides the empty state.any
prefixPrefix content.any
suffixSuffix content.any
insetLabelA label inside the select box; it is not rendered when prefix is also given.any
topSlotExtra content at the top of the dropdown.any
bottomSlotExtra content at the bottom of the dropdown.any

Exposes

NameDescriptionType
None

The Vue Cascader has no className prop: undeclared attributes such as class, style and data-* fall on the root element (the trigger), and aria-label, aria-labelledby, aria-describedby, aria-errormessage, aria-invalid and aria-required fall on the current combobox (see ARIA). change, select, focus, blur and keydown are component events and hide the native events of the same name on the root element; focus and blur are emitted when the dropdown opens and closes, they are not DOM focus events.

CascaderData

PropertiesInstructionstypeDefault
childrenchildren nodeCascaderData[]-
disabledDisabled statusboolean-
isLeafleaf nodeboolean-
labelText to be displayed (required), use the #label slot for custom contentstring|number-
loadingloadingboolean-
valueValue property (required)string|number-

Methods

Cascader exposes open(), close(), focus(), blur() and search(value) through a template ref, typed as CascaderHandle. search searches as if the value were typed; set filterTreeNode to enable search, and searchPosition to custom to customize the display search box.

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

    const cascader = useTemplateRef<CascaderHandle>('cascader');
    const open = () => cascader.value?.open();
</script>

<template>
    <Cascader ref="cascader" :treeData="treeData" />
</template>

Accessibility

ARIA

  • Cascader supports importing aria-label, aria-describedby, aria-errormessage, aria-invalid, aria-labelledby, aria-required to indicate the relevant information of the Cascader;
  • These aria-* attributes fall on the current combobox: the root element (the trigger) without search, the search input with search, and the tag input in multiple mode when the search input is in the trigger;
  • The combobox takes its name from, in order: aria-labelledby, aria-label, the rendered insetLabel (the combobox's aria-labelledby points at it, with an id the component generates when insetLabelId is not given; with a prefix the insetLabel is not rendered and does not name it), and otherwise "Cascader" from the locale. The tree or the search-result listbox in the dropdown uses the same name source;
  • Following the ARIA 1.2 combobox pattern, exactly one element is the combobox at any time; its aria-controls points at the list in the dropdown, and the dropdown has the combobox's name. Without search the trigger is a select-only combobox and the selected path is its value; with search the input itself is the combobox, with aria-autocomplete="list", and the trigger has no role and no Tab stop. In single mode the input appears only while searching, so the trigger is the combobox otherwise; the input is empty until the user types, so the selected path is linked to it as its description with aria-describedby; picking a result returns focus to the trigger;
  • The columns together are one role="tree": options are treeitems with aria-level (their column), aria-setsize, aria-posinset and aria-disabled, and aria-expanded on options that can expand; a single-choice Cascader uses aria-selected, a multi-choice one uses aria-checked (mixed when half checked);
  • Search results are a role="listbox" of options, with aria-selected for selected, or checked in a multi-choice Cascader. When #filterItem renders a single element root, that element gets an id, role="option" and these states; otherwise the component wraps the content in an li that has them;
  • The tick and the checkbox in front of an option only picture its state: they are hidden from screen readers and take no focus.

Keyboard Interaction

Cascader follows the WAI-ARIA Combobox pattern: focus always stays on the trigger or the search input, aria-activedescendant points at the current option, and the current option shows the focus ring. There is no current option when the dropdown opens.

  • While the dropdown is closed: ArrowDown, ArrowUp and Enter open it; without search, Space also opens it, and Home and End open it and move to the first or last option of the first column. With search, Space, Home and End edit the text
  • Enter: with a current option, act like a click on it, or check and uncheck it in a multi-choice Cascader; disabled options do not respond
  • ArrowDown, ArrowUp: move to the next or previous option in the current option's column (or in the search results); the first ArrowDown moves to the first option of the first column; Home, End: move to the first or last option of that column
  • ArrowRight: expand the current option, showing its column; once expanded, move to the first option of that column (swapped with ArrowLeft in RTL)
  • ArrowLeft: collapse the expanded current option; otherwise move to the parent option in the previous column
  • Space: with a current option, the same as Enter (it types a space in the search input)
  • Typing characters: while the dropdown is open, move to the next option after the current one, across the shown columns, whose label starts with the typed text, leaving the columns as they are; characters typed in quick succession form one search string. In the search input the characters are typed as text; a closed dropdown ignores them
  • Esc: close the dropdown

While focus is in the search input and text is typed, only ArrowDown, ArrowUp and Enter move and select; the other keys edit the text. The arrow keys only move the current option: unlike hovering, they do not expand the next column or trigger loadData, which loads when ArrowRight expands.

With showClear, a selected value or search text, and no disabled, the trigger renders a clear button. It is a native button named "Clear selection" from the locale, shown on hover, while the dropdown is open, or while focus is inside the Cascader. From the trigger or the search input, Tab moves to the clear button; Enter or Space clears, as a mouse click does, and the focus goes back to the trigger or the search input. In multiple mode Backspace in the search input also removes tags.

Content Guidelines

  • Selector options
    • If there is no default option, use "Select" as the placeholder text
    • Arrange options in alphabetical order or another logical order, so that users can find them more easily
    • Write in sentence case (capitalize the first letter, lowercase the rest) and avoid ending with commas or semicolons
    • Clearly express the purpose of the choice that each option represents

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-cascader_option-bgvar(--semi-transition_duration-none)
$transition_function-cascader_option-bgvar(--semi-transition_function-easeIn)
$transition_delay-cascader_option-bgvar(--semi-transition_delay-none)
$radius-cascadervar(--semi-border-radius-small)级联选择触发器圆角
$radius-cascader_option_emptyvar(--semi-border-radius-medium)级联选择菜单空状态圆角
$color-cascader_default-border-defaulttransparent级联选择触发器描边颜色 - 默认
$color-cascader_default-border-hovertransparent级联选择触发器描边颜色 - 悬浮
$color-cascader_default-border-focusvar(--semi-color-focus-border)级联选择触发器描边颜色 - 选中
$color-cascader_default-bg-defaultvar(--semi-color-fill-0)级联选择触发器背景颜色 - 默认
$color-cascader_default-bg-hovervar(--semi-color-fill-1)级联选择触发器背景颜色 - 悬浮
$color-cascader_default-bg-activevar(--semi-color-fill-2)级联选择触发器背景颜色 - 按下
$color-cascader_option_list-border-defaultvar(--semi-color-fill-0)级联选择各级菜单分割线颜色
$color-cascader_option_main-text-defaultvar(--semi-color-text-0)级联选择菜单项文字颜色
$color-cascader_option-bg-hovervar(--semi-color-fill-0)级联选择菜单项背景颜色 - 悬浮
$color-cascader_option-bg-activevar(--semi-color-fill-1)级联选择菜单项背景颜色 - 按下
$color-cascader_option-bg-selectedvar(--semi-color-primary-light-default)级联选择菜单项背景颜色 - 命中
$color-cascader_option_disabled-text-defaultvar(--semi-color-disabled-text)级联选择菜单项文字颜色 - 禁用
$color-cascader_option_disabled-bg-hovertransparent级联选择菜禁用单项背景颜色 - 悬浮
$color-cascader_option_disabled-bg-activetransparent级联选择菜禁用单项背景颜色 - 按下
$color-cascader_tag_disabled-bg-defaulttransparent级联选择菜禁用Tag背景颜色
$color-cascader_warning-bg-defaultvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色
$color-cascader_warning-border-defaultvar(--semi-color-warning-light-default)级联选择触发器警告描边颜色
$color-cascader_warning-bg-hovervar(--semi-color-warning-light-hover)级联选择触发器警告背景颜色 - 悬浮
$color-cascader_warning-border-hovervar(--semi-color-warning-light-hover)级联选择触发器警告描边颜色 - 悬浮
$color-cascader_warning-bg-focusvar(--semi-color-warning-light-default)级联选择触发器警告背景颜色 - 选中
$color-cascader_warning-border-focusvar(--semi-color-warning)级联选择触发器警告描边颜色 - 选中
$color-cascader_warning-bg-activevar(--semi-color-warning-light-active)级联选择触发器警告背景颜色 - 按下
$color-cascader_warning-border-activevar(--semi-color-warning-light-active)级联选择触发器警告描边颜色 - 按下
$color-cascader_danger-bg-defaultvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色
$color-cascader_danger-border-defaultvar(--semi-color-danger-light-default)级联选择触发器危险描边颜色
$color-cascader_danger-bg-hovervar(--semi-color-danger-light-hover)级联选择触发器危险背景颜色 - 悬浮
$color-cascader_danger-border-hovervar(--semi-color-danger-light-hover)级联选择触发器危险描边颜色 - 悬浮
$color-cascader_danger-bg-focusvar(--semi-color-danger-light-default)级联选择触发器危险背景颜色 - 选中
$color-cascader_danger-border-focusvar(--semi-color-danger)级联选择触发器危险描边颜色 - 选中
$color-cascader_danger-bg-activevar(--semi-color-danger-light-active)级联选择触发器危险背景颜色 - 按下
$color-cascader_danger-border-activevar(--semi-color-danger-light-active)级联选择触发器危险描边颜色 - 按下
$color-cascader_prefix_suffix_text-defaultvar(--semi-color-text-2)级联选择前后缀文字颜色
$spacing-cascader_selection-paddingLeft12px级联选择触发器左侧内边距 - 默认
$spacing-cascader_selection-paddingRight12px级联选择触发器右侧内边距 - 默认
$spacing-cascader_small_selection-paddingLeft12px级联选择触发器左侧内边距 - 小尺寸
$spacing-cascader_small_selection-paddingRight12px级联选择触发器右侧内边距 - 小尺寸
$spacing-cascader_large_selection-paddingLeft12px级联选择触发器左侧内边距 - 大尺寸
$spacing-cascader_large_selection-paddingRight12px级联选择触发器右侧内边距 - 大尺寸
$spacing-cascader_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 默认
$spacing-cascader_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时右侧内边距 - 默认
$spacing-cascader_small_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_small_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 小尺寸
$spacing-cascader_large_selection_multiple-paddingLeft$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_large_selection_multiple-paddingRight$spacing-extra-tight级联选择触发器多选时左侧内边距 - 大尺寸
$spacing-cascader_selection_tag-marginLeft$spacing-none级联选择触发器多选时标签的水平左外边距
$spacing-cascader_selection_tag-marginRight$spacing-super-tight级联选择触发器多选时标签的水平右外边距
$spacing-cascader_selection_tag-marginY1px级联选择触发器多选时标签的垂直外边距
$spacing-cascader_selection_tagInput-marginLeft-$spacing-extra-tight级联选择触发器多选搜索时 TagInput 的左外边距
$spacing-cascader_selection_input-marginLeft$spacing-extra-tight级联选择触发器多选搜索时输入框的左外边距
$spacing-cascader_selection_n-marginRight$spacing-extra-tight超出 maxTagCount 后,+n 的右外边距
$spacing-cascader_option-icon-marginLeft8px级联选择菜单项图标左侧外边距
$color-cascader_selection_n-text-defaultvar(--semi-color-text-0)超出 maxTagCount 后,+n 的文字默认颜色
$color-cascader_selection_n-text-disabledvar(--semi-color-disabled-text)超出 maxTagCount 后,+n 的文字disabled颜色
$color-cascader_selection_text_inactivevar(--semi-color-text-2)级联选择单选inpu输入框和text并存时,text颜色
$color-cascader_selection-text-defaultvar(--semi-color-text-0)级联选择选中项文字颜色
$color-cascader_placeholder-text-defaultvar(--semi-color-text-2)级联选择未选中项文字颜色
$color-cascader-icon-defaultvar(--semi-color-text-2)级联选择触发器图标颜色 - 默认
$color-cascader-icon-hovervar(--semi-color-primary-hover)级联选择触发器图标颜色 - 悬浮
$color-cascader-icon-activevar(--semi-color-primary-active)级联选择触发器图标颜色 - 按下
$color-cascader_option-icon-default$color-cascader-icon-default级联选择菜单项图标颜色
$color-cascader_input_disabled-bg-defaultvar(--semi-color-disabled-fill)级联选择禁用态背景颜色
$color-cascader_input_disabled-bg-hovervar(--semi-color-disabled-fill)级联选择禁用态背景颜色 - 悬浮
$color-cascader_input_disabled-text-defaultvar(--semi-color-disabled-text)级联选择禁用态文字颜色
$color-cascader_input_disabled-icon-defaultvar(--semi-color-disabled-text)级联选择禁用态图标颜色
$color-cascader_search-border-defaultvar(--semi-color-fill-0)级联选择搜索描边颜色
$color-cascader_option_empty-text-defaultvar(--semi-color-disabled-text)级联选择空状态文字颜色
$color-cascader_input-bg-defaulttransparent级联选择输入框背景颜色
$color-cascader_input-border-defaultnone级联选择输入框描边颜色
$color-cascader_select_highlightvar(--semi-color-primary)级联选择器搜索命中后文字高亮的颜色
$font-cascader_select-fontWeight$font-weight-bold级联选择菜单项字重 - 选中
$font-cascader-fontWeight$font-weight-regular级联选择菜单项字重 - 未选中
$font-cascader_selection_n-fontSize$font-size-small超出 maxTagCount 后,+n 的文字尺寸
$font-cascader_prefix_suffix_fontWeight$font-weight-bold级联选择 prefix/suffix 文字字重
$width-cascader-border1px级联选择触发器描边宽度
$width-cascader_option_list-border1px级联选择各级菜单分割线宽度
$width-cascader80px级联选择触发器最小宽度
$height-cascader_default$height-control-default级联选择触发器高度 - 默认
$height-cascader_small$height-control-small级联选择触发器高度 - 小尺寸
$height-cascader_large$height-control-large级联选择触发器高度 - 大尺寸
$width-cascader_hover-border$width-cascader-border级联选择触发器描边宽度 - 悬浮
$width-cascader_focus-border$border-thickness-control-focus级联选择触发器描边宽度 - 选中态
$width-cascader_search-border1px级联选择触搜索描边宽度
$width-cascader-icon32px级联选择触发器图标尺寸
$width-cascader-option-icon16px级联选择菜单项图标尺寸
$width-cascader_option150px级联选择各级菜单宽度
$height-cascader_option_list180px级联选择菜单高度
$height-cascader_selection_tagInput_wrapper_small22px级联选择多选搜索时搜索框最小高度 - 小尺寸
$height-cascader_selection_tagInput_wrapper_default30px级联选择多选搜索时搜索框最小高度 - 默认尺寸
$height-cascader_selection_tagInput_wrapper_large38px级联选择多选搜索时搜索框最小高度 - 大尺寸
$height-cascader_selection_wrapper_small22px级联选择单选搜索时搜索框高度 - 小尺寸
$height-cascader_selection_wrapper30px级联选择单选搜索时搜索框高度 - 默认尺寸
$height-cascader_selection_wrapper_large38px级联选择单选搜索时搜索框高度 - 大尺寸
$spacing-cascader_text-marginX$spacing-base-tight级联选择 prefix/suffix 文字水平内间距
$spacing-cascader_icon-marginX$spacing-tight级联选择 prefix/suffix 图标水平内间距
$spacing-cascader_label_checkbox-marginRight$spacing-tight级联选择 checkbox 的右间距
$spacing-cascader_selection_n-paddingX$spacing-tight级联选择 +N 的水平间距
$spacing-cascader_flatten_list-paddingRight64px级联选择搜索结果菜单右侧内边距
$spacing-cascader_empty_icon-marginRight8px级联选择搜索结果菜单
$spacing-cacader_option_list-paddingY4px级联选择下拉菜单列表垂直内边距
$spacing-cacader_option_list-paddingX0px级联选择下拉菜单列表水平内边距
$spacing-cascader_search-paddingX12px级联选择搜索水平内边距
$spacing-cascader_search-paddingY8px级联选择搜索垂直内边距
$spacing-cascader_option_empty-paddingY8px级联选择空结果垂直内边距
$spacing-cascader_option_empty-paddingX12px级联选择空结果水平内边距
$spacing-cascader_option-paddingTop8px级联选择菜单项顶部内边距
$spacing-cascader_option-paddingBottom8px级联选择菜单项底部内边距
$spacing-cascader_option-paddingLeft12px级联选择菜单项左侧内边距
$spacing-cascader_option-paddingRight16px级联选择菜单项右侧内边距