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
import { Cascader } from '@fibrous/semi-ui-solid';
Basic Usage
Basic usage, only leaf nodes can be selected by default.
Code
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" />;
}Multiple
Set multiple to make multiple selections.
Code
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader defaultValue={['zhejiang', 'ningbo', 'jiangbei']} style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" multiple />;
}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 renderLabel. 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:
renderLabel 自定义选项内容,仍对 label 文本进行搜索
Code
import { IconMapPinStroked } from '@fibrous/semi-icons-solid';
import { Cascader, type CascaderData, Typography } from '@fibrous/semi-ui-solid';
// The label inside an option must not be focusable, so the node only adds a decorative icon to the text
const renderLabel = (item: CascaderData) => (
<span style={{ display: 'inline-flex', 'align-items': 'center', 'column-gap': '4px' }}>
<IconMapPinStroked />
{item.label}
</span>
);
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<div>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="默认对label值进行搜索" filterTreeNode />
<br />
<br />
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="对value值进行搜索" filterTreeNode treeNodeFilterProp="value" />
<br />
<br />
<Typography.Title heading={6}>filterLeafOnly=false:</Typography.Title>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="filterLeafOnly=false" filterTreeNode filterLeafOnly={false} />
<br />
<br />
<Typography.Title heading={6}>renderLabel 自定义选项内容,仍对 label 文本进行搜索</Typography.Title>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="renderLabel" filterTreeNode renderLabel={renderLabel} />
</div>
);
}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
import { createSignal } from 'solid-js';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
const [value, setValue] = createSignal<CascaderValue>(['zhejiang', 'ningbo', 'haishu']);
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" value={value()} multiple filterTreeNode onChange={(newValue) => setValue(newValue)} />;
}Filtered data can be sorted using filterSorter.
Code
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
];
export default function CascaderDemo() {
return (
<div>
<Cascader
style={{ width: '300px' }}
treeData={treeData}
placeholder="输入 s 查看排序效果"
filterTreeNode
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 String(firstData.label) < String(lastData.label) ? -1 : 1;
}
}}
/>
</div>
);
}If you want to customize the rendering options after the search, you can use filterRender to achieve custom rendering of the entire line. The parameter has the type CascaderFilterRenderProps:
interface CascaderFilterRenderProps {
class: string; // Class of the option, bind it to the root element of the returned element
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: (e: MouseEvent) => void; // Callback when clicked option in single selection
onCheck: (e: CheckboxEvent) => void; // Callback when clicked option in multiple selection, the signature of the onChange of Checkbox
}
When the returned element is a single element, it gets the id, role="option" and the selected state of the option; when it returns text or several nodes, the component wraps them in an li.
The example is as follows
鼠标 hover 到选项可查看被省略文本完整内容
Code
import { Cascader, Checkbox, type CascaderFilterRenderProps, Typography } from '@fibrous/semi-ui-solid';
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 renderSearchOptionSingle = (props: CascaderFilterRenderProps) => (
<li class={props.class} style={{ 'justify-content': 'flex-start' }} role="option" aria-selected={props.selected} onClick={(e) => props.onClick(e)}>
<Typography.Text ellipsis={{ showTooltip: { opts: { style: { 'word-break': 'break-all' } } } }} style={{ width: '270px', color: props.selected ? 'var(--semi-color-primary)' : undefined }}>
{props.data.map((item) => item.label).join(' / ')}
</Typography.Text>
</li>
);
const renderSearchOptionMultiple = (props: CascaderFilterRenderProps) => {
const label = () => props.data.map((item) => item.label).join(' / ');
return (
<li class={props.class} style={{ 'justify-content': 'flex-start' }} role="option" aria-label={label()} aria-selected={props.checkStatus.checked}>
<Checkbox tabIndex={-1} onChange={props.onCheck} indeterminate={props.checkStatus.halfChecked} checked={props.checkStatus.checked}>
<Typography.Text ellipsis={{ showTooltip: { opts: { style: { 'word-break': 'break-all' } } } }} style={{ width: '250px' }}>
{label()}
</Typography.Text>
</Checkbox>
</li>
);
};
export default function CascaderDemo() {
return (
<div>
<p>鼠标 hover 到选项可查看被省略文本完整内容</p>
<br />
<Cascader style={{ width: '320px' }} treeData={treeData} placeholder="单选,输入 s 自定义搜索选项渲染结果" filterTreeNode filterRender={renderSearchOptionSingle} />
<br />
<Cascader multiple style={{ width: '320px', 'margin-top': '20px' }} treeData={treeData} placeholder="多选,输入 s 自定义搜索选项渲染结果" filterTreeNode filterRender={renderSearchOptionMultiple} />
</div>
);
}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
Code
import { Cascader, Checkbox, type CascaderFilterRenderProps, Typography } from '@fibrous/semi-ui-solid';
const treeData = ['通用', '场景'].map((label, m) => ({
label: label,
value: m,
children: Array.from({ length: 100 }, (_item, n) => ({
value: `${m}-${n}`,
label: `${m}-${n} 第二级`,
children: Array.from({ length: 20 }, (_item, o) => ({
value: `${m}-${n}-${o}`,
label: `${m}-${n}-${o} 第三级详细内容`,
})),
})),
}));
const virtualize = {
// Height = default panel 180px minus 2 * 8px padding
height: 172,
width: 320,
itemSize: 36,
};
const renderFilterItem = (props: CascaderFilterRenderProps) => (
<div class={props.class} style={{ 'justify-content': 'start', padding: '8px 16px 8px 12px', 'box-sizing': 'border-box' }}>
<Checkbox onChange={props.onCheck} indeterminate={props.checkStatus.halfChecked} checked={props.checkStatus.checked} style={{ 'margin-right': '8px' }} />
<Typography.Text ellipsis={{ showTooltip: { opts: { style: { 'word-break': 'break-all' } } } }} style={{ 'max-width': '260px' }}>
{props.data.map((item) => item.label).join(' | ')}
</Typography.Text>
</div>
);
export default function CascaderDemo() {
return <Cascader multiple filterTreeNode style={{ width: '320px' }} treeData={treeData} placeholder="输入 通用 or 场景 进行搜索" virtualizeInSearch={virtualize} filterRender={renderFilterItem} />;
}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
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<Cascader
style={{ width: '300px' }}
treeData={treeData}
placeholder="请选择所在地区"
multiple
showRestTagsPopover
restTagsPopoverProps={{ position: 'top' }}
maxTagCount={1}
defaultValue={[
['zhejiang', 'ningbo', 'haishu'],
['zhejiang', 'hangzhou', 'xihu'],
]}
/>
);
}Limit Tags Number
In a multi-selection scene, use max to limit the number of multi-selection selections. After max is exceeded, the onExceed callback will be triggered.
Code
import { Cascader, Toast } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<Cascader
style={{ width: '300px' }}
treeData={treeData}
placeholder="请选择所在地区"
multiple
max={1}
onExceed={(v) => {
Toast.warning('exceed max');
console.log(v);
}}
defaultValue={[['zhejiang', 'ningbo', 'haishu']]}
/>
);
}Change on Select
In the case of single selection, you can also set changeOnSelect to allow the parent option to be selected.
Code
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<div>
<Cascader style={{ width: '300px' }} treeData={treeData} changeOnSelect placeholder="选择即改变" />
<br />
<br />
<Cascader style={{ width: '300px' }} treeData={treeData} changeOnSelect placeholder="可搜索的选择即改变" filterTreeNode />
</div>
);
}Custom Display Property
Set displayProp to select which property in the data you would like to display. By default, label is displayed.
单选
多选
Code
import { Cascader, Typography } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<>
<Typography.Title heading={6}>单选</Typography.Title>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="回填时显示数据的value值" displayProp="value" defaultValue={['zhejiang', 'ningbo', 'jiangbei']} />
<br />
<br />
<Typography.Title heading={6}>多选</Typography.Title>
<Cascader multiple style={{ width: '300px' }} treeData={treeData} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} placeholder="回填时显示数据的value值" displayProp="value" />
</>
);
}The return format can be set by setting displayRender.
When single selection (multiple=false), displayRender((labelPath: Array<string | number>) => JSX.Element), where labelPath is a path array composed of labels.
When multiple selection (multiple=true), displayRender((item: Entity, index: number) => JSX.Element), where item is the relevant data of the node, and Entity is listed below.
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
}
单选
多选
Code
import type { JSX } from 'solid-js';
import { Cascader, Tag, Typography } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<>
<Typography.Title heading={6}>单选</Typography.Title>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="自定义回填时显示数据的格式" displayRender={(list) => '已选择:' + list.join(' -> ')} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} />
<br />
<br />
<Typography.Title heading={6}>多选</Typography.Title>
<Cascader
multiple
style={{ width: '300px' }}
treeData={treeData}
defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
placeholder="自定义回填时显示数据的格式"
displayRender={(item) => (
<Tag style={{ 'margin-right': '4px' }} color="white">
{item.data.label as JSX.Element}
</Tag>
)}
/>
</>
);
}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
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader style={{ width: '300px' }} treeData={treeData} defaultValue={['zhejiang', 'ningbo', 'jiangbei']} filterTreeNode separator=" > " />;
}Disabled
Code
import { Cascader } from '@fibrous/semi-ui-solid';
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<div>
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" disabled />
<br />
<br />
<Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" defaultValue={['zhejiang', 'hangzhou', 'xihu']} filterTreeNode disabled />
</div>
);
}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
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader style={{ width: '300px' }} treeData={treeData} multiple placeholder="请选择所在地区" disableStrictly />;
}the Way of Expand Menu
You can use showNext to set the time to expand the Dropdown submenu, optional: click (default), hover.
Code
import { Cascader } from '@fibrous/semi-ui-solid';
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
{
label: '临安区',
value: 'linan',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" showNext="hover" />;
}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
import { Cascader } from '@fibrous/semi-ui-solid';
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{
label: '西湖区',
value: 'xihu',
},
{
label: '萧山区',
value: 'xiaoshan',
},
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{
label: '海曙区',
value: 'haishu',
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader style={{ width: '300px' }} treeData={treeData} multiple placeholder="请选择所在地区" showNext="hover" clickToSelect />;
}Additional items
We have reserved slots at the top and bottom of the cascade selector. You can set them through topSlot or bottomSlot.
Code
import { Cascader, Typography } from '@fibrous/semi-ui-solid';
const slotStyle = {
height: '36px',
display: 'flex',
padding: '0 32px',
'align-items': 'center',
cursor: 'pointer',
'border-top': '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',
},
],
},
],
},
];
export default function CascaderDemo() {
return (
<Cascader
style={{ width: '300px' }}
treeData={treeData}
placeholder="请选择所在地区"
bottomSlot={
<div style={slotStyle}>
<Typography.Text>找不到相关选项?</Typography.Text>
<Typography.Text link>去新建</Typography.Text>
</div>
}
/>
);
}Controlled Component
You can use value along with onChange property if you want to use Cascader as a controlled component. Changes made in place to a controlled array are not noticed; pass a new array to update the selection.
Code
import { createSignal } from 'solid-js';
import { Cascader, type CascaderSimpleValue } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
const [value, setValue] = createSignal<CascaderSimpleValue[]>([]);
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" value={value()} onChange={(newValue) => setValue(newValue)} />;
}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
import { createSignal } from 'solid-js';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
const [value, setValue] = createSignal<CascaderValue>([]);
const onChange = (newValue: CascaderValue) => {
console.log(newValue);
setValue(newValue);
};
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="autoMergeValue 为 false" value={value()} multiple autoMergeValue={false} onChange={onChange} />;
}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 onChange parameter value only include leaf nodes.
Code
import { createSignal } from 'solid-js';
import { Cascader, type CascaderValue } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
];
export default function CascaderDemo() {
const [value, setValue] = createSignal<CascaderValue>([]);
const onChange = (newValue: CascaderValue) => {
console.log(newValue);
setValue(newValue);
};
return <Cascader style={{ width: '300px' }} treeData={treeData} placeholder="开启 leafOnly" value={value()} multiple leafOnly onChange={onChange} />;
}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
import { Cascader } from '@fibrous/semi-ui-solid';
const treeData = [
{
label: '亚洲',
value: 'Asia',
children: [
{
label: '中国',
value: 'China',
children: [
{
label: '北京',
value: 'Beijing',
},
{
label: '上海',
value: 'Shanghai',
},
],
},
],
},
{
label: '北美洲',
value: 'North America',
},
];
export default function CascaderDemo() {
return <Cascader multiple defaultValue={[['Asia'], ['Asia', 'China', 'Beijing']]} checkRelation="unRelated" style={{ width: '300px' }} treeData={treeData} />;
}Dynamic Update of Data
Replace treeData as a whole to update the data; a new array with equal content keeps the expanded and selected state.
Code
import { createSignal } from 'solid-js';
import { Button, Cascader, type CascaderData } from '@fibrous/semi-ui-solid';
export default function CascaderDemo() {
const [treeData, setTreeData] = createSignal<CascaderData[]>([]);
const add = () => {
const itemLength = Math.floor(Math.random() * 3) + 1;
const newTreeData = Array.from({ length: itemLength }, (_v, i) => {
const length = Math.floor(Math.random() * 3);
const children = Array.from({ length }, (_cv, ci) => ({
key: `${i}-${ci}`,
label: `Item-${i}-${ci}`,
value: `${i}-${ci}`,
}));
return {
key: `${i}`,
label: `Item-${i}`,
value: `${i}`,
children,
};
});
setTreeData(newTreeData);
};
return (
<>
<Cascader style={{ width: '300px' }} treeData={treeData()} placeholder="请选择" />
<br />
<br />
<Button onClick={add}>动态改变数据</Button>
</>
);
}Load Async Data
You could use loadData to load data asynchronously.
Could not be used together with searching
Code
import { createSignal } from 'solid-js';
import { Cascader, type CascaderData } from '@fibrous/semi-ui-solid';
const initialData: 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[] => {
return list.map((node) => {
if (node.value === value) {
return { ...node, children };
}
if (node.children) {
return { ...node, children: updateTreeData(node.children, value, children) };
}
return node;
});
};
export default function CascaderDemo() {
const [data, setData] = createSignal<CascaderData[]>(initialData);
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(() => {
setData((origin) =>
updateTreeData(origin, value, [
{
label: `${label} - 1`,
value: `${label}-1`,
isLeaf: selectedOpt.length > 1,
},
{
label: `${label} - 2`,
value: `${label}-2`,
isLeaf: selectedOpt.length > 1,
},
])
);
resolve();
}, 1000);
});
};
return <Cascader style={{ width: '300px' }} treeData={data()} loadData={onLoadData} placeholder="Please select" />;
}Remote Search
Cascader supports remote search. With remote enabled, search input no longer goes through local filtering; only the onSearch callback is fired, and you are responsible for fetching treeData asynchronously based on the input. This mirrors Select's remote behavior.
Code
import { createSignal } from 'solid-js';
import { debounce } from 'es-toolkit';
import { Cascader, type CascaderData, Spin } from '@fibrous/semi-ui-solid';
const fetchByKeyword = (keyword: string) =>
new Promise<CascaderData[]>((resolve) => {
const delay = 200 + Math.floor(Math.random() * 800);
setTimeout(() => {
if (!keyword) {
resolve([]);
return;
}
resolve([
{ label: `${keyword} - 选项 A`, value: `${keyword}-a` },
{ label: `${keyword} - 选项 B`, value: `${keyword}-b` },
{ label: `${keyword} - 选项 C`, value: `${keyword}-c` },
]);
}, delay);
});
export default function CascaderDemo() {
const [treeData, setTreeData] = createSignal<CascaderData[]>([]);
const [loading, setLoading] = createSignal(false);
const latest = { token: 0 };
const handleSearch = debounce((input: string) => {
if (!input) {
setTreeData([]);
setLoading(false);
return;
}
latest.token += 1;
const token = latest.token;
setLoading(true);
void fetchByKeyword(input).then((next) => {
// Drop stale results that arrive after a newer request
if (token !== latest.token) {
return;
}
setTreeData(next);
setLoading(false);
});
}, 300);
return (
<Spin spinning={loading()}>
<Cascader style={{ width: '300px' }} placeholder="输入关键词远程搜索" filterTreeNode remote treeData={treeData()} onSearch={handleSearch} onChange={(v) => console.log('selected:', v)} />
</Spin>
);
}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
import { Cascader } from '@fibrous/semi-ui-solid';
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',
},
],
},
],
},
],
},
],
},
],
},
];
export default function CascaderDemo() {
return <Cascader dropdownClassName="components-cascader-demo" style={{ width: '300px' }} treeData={treeData} placeholder="请选择所在地区" />;
}.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 triggerRender to customize the display of the select box.
The parameter of triggerRender has the type CascaderTriggerRenderProps, as follows:
interface CascaderTriggerRenderProps {
/* All props passed to Cascader by users, without ref */
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, and the display text of the selected path while closed */
inputValue: JSX.Element;
/**
* 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 triggerRender 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;
}
Read the parameter with its access form, such as props.value, and do not destructure it, or the reactivity is lost.
Code
import { type JSX, Show } from 'solid-js';
import { IconChevronDown, IconClose } from '@fibrous/semi-icons-solid';
import { Button, ButtonGroup, Cascader, type CascaderData, type CascaderTriggerRenderProps, Tag, TagInput } from '@fibrous/semi-ui-solid';
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 as JSX.Element | undefined;
};
// The clear button is a sibling of the trigger button: buttons cannot nest
const triggerRenderSingle = (props: CascaderTriggerRenderProps) => (
<ButtonGroup theme="light">
<Button icon={<IconChevronDown />} iconPosition="right">
{props.value ? getLabelFromValue(props.value) : props.placeholder}
</Button>
<Show when={props.value}>
<Button
icon={<IconClose />}
aria-label="清除"
onClick={(e) => {
e.stopPropagation();
props.onClear(e);
}}
/>
</Show>
</ButtonGroup>
);
const triggerRenderMultiple = (props: CascaderTriggerRenderProps<true>) => (
<TagInput
value={[...props.value]}
onInputChange={(value) => props.onSearch(value)}
renderTagItem={(value) => (
<Tag tagKey={value} closable onClose={() => props.onRemove(value)} style={{ 'margin-left': '2px' }}>
{getLabelFromValue(value)}
</Tag>
)}
/>
);
export default function CascaderDemo() {
return (
<>
<Cascader treeData={treeData} placeholder="Custom Trigger" triggerRender={triggerRenderSingle} />
<br />
<Cascader triggerRender={triggerRenderMultiple} multiple filterTreeNode treeData={treeData} style={{ width: '300px' }} placeholder="Custom Trigger" />
</>
);
}API reference
Cascader
Props
| Property | Description | Type | Default |
|---|---|---|---|
| aria-label | Accessible name of the combobox. It comes after aria-labelledby and before the insetLabel content. | string | - |
| arrowIcon | Custom arrow icon on the right; null hides the arrow. | JSX.Element | - |
| autoAdjustOverflow | Whether the dropdown flips its direction when an edge covers it. | boolean | - |
| autoMergeValue | In multiple mode, once a parent is selected the value leaves out its descendants. When leafOnly is also on, leafOnly takes precedence. | boolean | true |
| borderless | Whether to use the borderless style. | boolean | false |
| bottomSlot | Content at the bottom of the dropdown. | JSX.Element | - |
| changeOnSelect | Whether a non-leaf node can be selected in single mode. | boolean | false |
| checkRelation | In multiple mode, how checked states relate: related links parents and children, unRelated keeps them independent. | 'related' | 'unRelated' | "related" |
| clearIcon | Custom icon of the clear button; effective when showClear is true. | JSX.Element | - |
| clickToSelect | In multiple mode, clicking any node selects it. Often used with showNext="hover": hovering expands the submenu and clicking selects the node. | boolean | false |
| defaultOpen | Whether the dropdown is open on mount. | boolean | false |
| defaultValue | The initially selected entry, read once on mount: a node or a path in single mode, an array of paths in multiple mode. | TypeModeValue<Multiple> | - |
| disabled | Whether the Cascader is disabled. | boolean | - |
| disableStrictly | Whether strict disabling is on. A disabled node then does not change when its parent or children are checked. | boolean | false |
| displayProp | The node field shown in the trigger and the tags. | string | "label" |
| displayRender | Custom trigger content. In single mode it receives the array of labels on the path; in multiple mode it receives the Entity of a node and its index, and returns the content of its tag. | TypeByMultiple<Multiple, (selected: Array<string | number>) => JSX.Element, (selected: Entity, idx: number) => JSX.Element> | - |
| dropdownClassName | Class of the dropdown menu. | string | - |
| dropdownStyle | Style of the dropdown menu: an object with kebab-case keys, or a string. | JSX.CSSProperties | string | - |
| emptyContent | Content shown when there is no option or the search has no result; defaults to the locale text for no data, and null hides it. | JSX.Element | - |
| enableLeafClick | In multiple mode, whether clicking the option of a leaf node checks it. | boolean | false |
| expandIcon | Custom icon that marks a node with children. | JSX.Element | - |
| filterLeafOnly | Whether the search results only show the paths of leaf nodes. | boolean | true |
| filterRender | Renders each search result; it receives CascaderFilterRenderProps. When a single element is returned, it gets the option's id, role="option" and the selected state; text or several nodes are wrapped in an li. | Type(props: FilterRenderProps) => JSX.Element | - |
| filterSorter | Sorts the search results. It receives two result paths (the node arrays from the root to the matched node) and the input, and returns a comparison value. | Type(first: CascaderData[], second: CascaderData[], inputValue: string) => number | - |
| filterTreeNode | Whether search is on. True matches the input against the treeNodeFilterProp value, case-insensitively. A match function receives the input, that string and the node data. | Type((inputValue: string, treeNodeString: string, data?: CascaderData) => boolean) | boolean | false |
| id | The id of the trigger, which is the root element. | string | - |
| insetLabel | Label inset in the trigger, which names the combobox; it is not rendered when prefix is set. | JSX.Element | - |
| insetLabelId | The id of the insetLabel content, used to name the combobox; the component generates one when it is not set. | string | - |
| keyMaps | Custom field names for value, label, children, disabled and isLeaf in a node. | BasicCascaderProps['keyMaps'] | - |
| leafOnly | In multiple mode, the value and the tags only include leaf nodes. | boolean | false |
| loadData | Loads data asynchronously. It receives the path from the root to the current node and must return a Promise. Cannot be used with search. | Type(selectOptions: CascaderData[]) => Promise<void> | - |
| max | In multiple mode, the most paths that can be selected; exceeding it calls onExceed. | number | - |
| maxTagCount | In multiple mode, the most tags shown; the rest appear as +N. | number | - |
| motion | Whether the dropdown animates when it opens. | boolean | true |
| multiple | Whether several paths can be selected. It changes the types of value, defaultValue, onChange, onSelect, displayRender and triggerRender; it has no global default. | Multiple | false |
| onBlur | Called when focus leaves or the dropdown closes; it is not the DOM blur event. Closing with the ref's close() passes no argument. | Type(e?: MouseEvent | KeyboardEvent) => void | - |
| onChange | Called when the selection changes, with the value path of the selection, or an array of paths in multiple mode; with onChangeWithObject set to true it receives node data. | Type(value: ByMultiple<Multiple, Array<SimpleValueType>, Array<Array<SimpleValueType>>>) => void | - |
| onChangeWithObject | When true, onChange receives node data, which includes the value key, instead of values; a controlled value and defaultValue must then be node data too. | boolean | - |
| onClear | Called when the clear button is clicked. | Type() => void | - |
| onDropdownVisibleChange | Called when the dropdown opens or closes, with whether it is visible. | Type(visible: boolean) => void | - |
| onExceed | In multiple mode, called when max is exceeded, with the array of checked Entity. | Type(checkedItem: Entity[]) => void | - |
| onFocus | Called when the combobox gets focus, or the dropdown is opened by the mouse or the keyboard; it is not the DOM focus event. | Type(e: MouseEvent | KeyboardEvent) => void | - |
| onListScroll | Called when a column scrolls, with the event and { panelIndex, activeNode }; scrolling the search results does not call it. | Type(e: Event, panel: ScrollPanelProps) => void | - |
| onLoad | Called when a node finishes loading, with the set of newly loaded keys and the data of that node. | Type(newLoadedKeys: Set<string>, data: CascaderData) => void | - |
| onSearch | Called when the search text changes, with the input. | Type(value: string) => void | - |
| onSelect | Called when a node is selected, with its value; in multiple mode a single checked node gives its value, and more give an array. | Type(value: ByMultiple<Multiple, string | number, string | number | Array<string | number>>) => void | - |
| placeholder | Placeholder of the select box. | string | - |
| position | Direction of the dropdown, bottomLeft by default and bottomRight in RTL. | Position | - |
| prefix | Prefix shown on the left inside the trigger. | JSX.Element | - |
| preventScroll | Whether focusing inside the component stops the browser scrolling the document to show the focused element. | boolean | - |
| ref | Receives the trigger element, which carries the open, close, focus, blur and search methods listed under Methods. | TypeRef<CascaderElement> | - |
| remote | Whether to search remotely. Input is then not filtered locally; only onSearch is called, and the caller updates treeData asynchronously. | boolean | false |
| renderLabel | Renders the content of an option in the columns; it receives the node data. The search results, the selection and the tags still show the node's label text. | Type(item: CascaderData) => JSX.Element | - |
| restTagsPopoverProps | Props of the Popover that shows the remaining tags, such as position and trigger; the type is PopoverProps. | PopoverProps | - |
| searchPlaceholder | Placeholder of the search input; placeholder is used when it is not set. | string | - |
| searchPosition | Where the search input is: trigger puts it in the trigger, custom leaves it to the caller, who passes the keyword with the ref's search method. | 'trigger' | 'custom' | "trigger" |
| separator | Separator between the levels of a path, used in the search results and in the trigger in single mode. | string | " / " |
| showClear | Whether to show the clear button. | boolean | false |
| showNext | When a submenu expands: click or hover. | 'click' | 'hover' | "click" |
| showRestTagsPopover | Once maxTagCount is exceeded, whether hovering +N shows the remaining tags in a Popover. | boolean | false |
| size | Size of the select box: small, default or large. Inside an InputGroup the group's size takes its place. | CascaderType | - |
| stopPropagation | Whether clicks on the dropdown stop bubbling. | boolean | string | true |
| suffix | Suffix shown on the right inside the trigger. | JSX.Element | - |
| topSlot | Content at the top of the dropdown. | JSX.Element | - |
| treeData | Node data; see CascaderData. | TypeArray<CascaderData> | [] |
| treeNodeFilterProp | The node field matched against the search input. | string | "label" |
| triggerRender | Renders a custom trigger; it receives TriggerRenderProps, and the element it returns replaces the default trigger content. | Type(props: TriggerRenderProps<Multiple>) => JSX.Element | - |
| validateStatus | Validation status of the trigger: default, error or warning; it only affects the style. | ValidateStatus | - |
| value | The selected entry under control, typed like defaultValue. Passing it makes the component controlled, to be updated with onChange. | TypeModeValue<Multiple> | - |
| virtualizeInSearch | Virtual list of the search results, made of height, width and itemSize, for large result sets; height defaults to 180. | Virtualize | - |
Methods
| Method | Description | Type |
|---|---|---|
| blur | Removes focus from the combobox. | Type() => void |
| close | Closes the dropdown. | Type() => void |
| focus | Focuses the combobox, which is the search input or the trigger, without opening the dropdown. | Type() => void |
| open | Opens the dropdown. | Type() => void |
| search | Searches as if the text were typed; needs filterTreeNode, and with searchPosition set to custom the caller draws the search input. | Type(value: string) => void |
Cascader puts class, style, ref and undeclared native attributes on the root element (the trigger), and aria-label, aria-labelledby, aria-describedby, aria-errormessage, aria-invalid and aria-required on the current combobox (see ARIA). onFocus and onBlur are emitted when the dropdown opens and closes; they are not DOM focus events.
CascaderData
| Properties | Instructions | type | Default |
|---|---|---|---|
| children | children node | CascaderData[] | - |
| disabled | Disabled status | boolean | - |
| isLeaf | leaf node | boolean | - |
| label | Text to be displayed (required), use renderLabel for custom content | string|number | - |
| loading | loading | boolean | - |
| value | Value property (required) | string|number | - |
Methods
The ref of Cascader points at the trigger element, which carries the open(), close(), focus(), blur() and search(value) methods, typed as CascaderElement. search searches as if the value were typed; set filterTreeNode to enable search, and searchPosition to custom to customize the display search box.
import { Cascader, type CascaderElement } from '@fibrous/semi-ui-solid';
let cascader: CascaderElement | undefined;
const open = () => cascader?.open();
<Cascader ref={(node) => (cascader = node)} treeData={treeData} />;
Accessibility
ARIA
- Cascader supports importing
aria-label,aria-describedby,aria-errormessage,aria-invalid,aria-labelledby,aria-requiredto 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'saria-labelledbypoints at it, with an id the component generates wheninsetLabelIdis 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-controlspoints 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, witharia-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 witharia-describedby; picking a result returns focus to the trigger; - The columns together are one
role="tree": options aretreeitems witharia-level(their column),aria-setsize,aria-posinsetandaria-disabled, andaria-expandedon options that can expand; a single-choice Cascader usesaria-selected, a multi-choice one usesaria-checked(mixedwhen half checked);
- Search results are a
role="listbox"ofoptions, witharia-selectedfor selected, or checked in a multi-choice Cascader. WhenfilterRenderreturns a single element, that element gets anid,role="option"and these states; otherwise the component wraps the content in anlithat 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,ArrowUpandEnteropen it; without search,Spacealso opens it, andHomeandEndopen it and move to the first or last option of the first column. With search,Space,HomeandEndedit 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 respondArrowDown,ArrowUp: move to the next or previous option in the current option's column (or in the search results); the firstArrowDownmoves to the first option of the first column;Home,End: move to the first or last option of that columnArrowRight: expand the current option, showing its column; once expanded, move to the first option of that column (swapped withArrowLeftin RTL)ArrowLeft: collapse the expanded current option; otherwise move to the parent option in the previous columnSpace: with a current option, the same asEnter(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
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-cascader_option-bg | var(--semi-transition_duration-none) | |
| $transition_function-cascader_option-bg | var(--semi-transition_function-easeIn) | |
| $transition_delay-cascader_option-bg | var(--semi-transition_delay-none) | |
| $radius-cascader | var(--semi-border-radius-small) | 级联选择触发器圆角 |
| $radius-cascader_option_empty | var(--semi-border-radius-medium) | 级联选择菜单空状态圆角 |
| $color-cascader_default-border-default | transparent | 级联选择触发器描边颜色 - 默认 |
| $color-cascader_default-border-hover | transparent | 级联选择触发器描边颜色 - 悬浮 |
| $color-cascader_default-border-focus | var(--semi-color-focus-border) | 级联选择触发器描边颜色 - 选中 |
| $color-cascader_default-bg-default | var(--semi-color-fill-0) | 级联选择触发器背景颜色 - 默认 |
| $color-cascader_default-bg-hover | var(--semi-color-fill-1) | 级联选择触发器背景颜色 - 悬浮 |
| $color-cascader_default-bg-active | var(--semi-color-fill-2) | 级联选择触发器背景颜色 - 按下 |
| $color-cascader_option_list-border-default | var(--semi-color-fill-0) | 级联选择各级菜单分割线颜色 |
| $color-cascader_option_main-text-default | var(--semi-color-text-0) | 级联选择菜单项文字颜色 |
| $color-cascader_option-bg-hover | var(--semi-color-fill-0) | 级联选择菜单项背景颜色 - 悬浮 |
| $color-cascader_option-bg-active | var(--semi-color-fill-1) | 级联选择菜单项背景颜色 - 按下 |
| $color-cascader_option-bg-selected | var(--semi-color-primary-light-default) | 级联选择菜单项背景颜色 - 命中 |
| $color-cascader_option_disabled-text-default | var(--semi-color-disabled-text) | 级联选择菜单项文字颜色 - 禁用 |
| $color-cascader_option_disabled-bg-hover | transparent | 级联选择菜禁用单项背景颜色 - 悬浮 |
| $color-cascader_option_disabled-bg-active | transparent | 级联选择菜禁用单项背景颜色 - 按下 |
| $color-cascader_tag_disabled-bg-default | transparent | 级联选择菜禁用Tag背景颜色 |
| $color-cascader_warning-bg-default | var(--semi-color-warning-light-default) | 级联选择触发器警告背景颜色 |
| $color-cascader_warning-border-default | var(--semi-color-warning-light-default) | 级联选择触发器警告描边颜色 |
| $color-cascader_warning-bg-hover | var(--semi-color-warning-light-hover) | 级联选择触发器警告背景颜色 - 悬浮 |
| $color-cascader_warning-border-hover | var(--semi-color-warning-light-hover) | 级联选择触发器警告描边颜色 - 悬浮 |
| $color-cascader_warning-bg-focus | var(--semi-color-warning-light-default) | 级联选择触发器警告背景颜色 - 选中 |
| $color-cascader_warning-border-focus | var(--semi-color-warning) | 级联选择触发器警告描边颜色 - 选中 |
| $color-cascader_warning-bg-active | var(--semi-color-warning-light-active) | 级联选择触发器警告背景颜色 - 按下 |
| $color-cascader_warning-border-active | var(--semi-color-warning-light-active) | 级联选择触发器警告描边颜色 - 按下 |
| $color-cascader_danger-bg-default | var(--semi-color-danger-light-default) | 级联选择触发器危险背景颜色 |
| $color-cascader_danger-border-default | var(--semi-color-danger-light-default) | 级联选择触发器危险描边颜色 |
| $color-cascader_danger-bg-hover | var(--semi-color-danger-light-hover) | 级联选择触发器危险背景颜色 - 悬浮 |
| $color-cascader_danger-border-hover | var(--semi-color-danger-light-hover) | 级联选择触发器危险描边颜色 - 悬浮 |
| $color-cascader_danger-bg-focus | var(--semi-color-danger-light-default) | 级联选择触发器危险背景颜色 - 选中 |
| $color-cascader_danger-border-focus | var(--semi-color-danger) | 级联选择触发器危险描边颜色 - 选中 |
| $color-cascader_danger-bg-active | var(--semi-color-danger-light-active) | 级联选择触发器危险背景颜色 - 按下 |
| $color-cascader_danger-border-active | var(--semi-color-danger-light-active) | 级联选择触发器危险描边颜色 - 按下 |
| $color-cascader_prefix_suffix_text-default | var(--semi-color-text-2) | 级联选择前后缀文字颜色 |
| $spacing-cascader_selection-paddingLeft | 12px | 级联选择触发器左侧内边距 - 默认 |
| $spacing-cascader_selection-paddingRight | 12px | 级联选择触发器右侧内边距 - 默认 |
| $spacing-cascader_small_selection-paddingLeft | 12px | 级联选择触发器左侧内边距 - 小尺寸 |
| $spacing-cascader_small_selection-paddingRight | 12px | 级联选择触发器右侧内边距 - 小尺寸 |
| $spacing-cascader_large_selection-paddingLeft | 12px | 级联选择触发器左侧内边距 - 大尺寸 |
| $spacing-cascader_large_selection-paddingRight | 12px | 级联选择触发器右侧内边距 - 大尺寸 |
| $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-marginY | 1px | 级联选择触发器多选时标签的垂直外边距 |
| $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-marginLeft | 8px | 级联选择菜单项图标左侧外边距 |
| $color-cascader_selection_n-text-default | var(--semi-color-text-0) | 超出 maxTagCount 后,+n 的文字默认颜色 |
| $color-cascader_selection_n-text-disabled | var(--semi-color-disabled-text) | 超出 maxTagCount 后,+n 的文字disabled颜色 |
| $color-cascader_selection_text_inactive | var(--semi-color-text-2) | 级联选择单选inpu输入框和text并存时,text颜色 |
| $color-cascader_selection-text-default | var(--semi-color-text-0) | 级联选择选中项文字颜色 |
| $color-cascader_placeholder-text-default | var(--semi-color-text-2) | 级联选择未选中项文字颜色 |
| $color-cascader-icon-default | var(--semi-color-text-2) | 级联选择触发器图标颜色 - 默认 |
| $color-cascader-icon-hover | var(--semi-color-primary-hover) | 级联选择触发器图标颜色 - 悬浮 |
| $color-cascader-icon-active | var(--semi-color-primary-active) | 级联选择触发器图标颜色 - 按下 |
| $color-cascader_option-icon-default | $color-cascader-icon-default | 级联选择菜单项图标颜色 |
| $color-cascader_input_disabled-bg-default | var(--semi-color-disabled-fill) | 级联选择禁用态背景颜色 |
| $color-cascader_input_disabled-bg-hover | var(--semi-color-disabled-fill) | 级联选择禁用态背景颜色 - 悬浮 |
| $color-cascader_input_disabled-text-default | var(--semi-color-disabled-text) | 级联选择禁用态文字颜色 |
| $color-cascader_input_disabled-icon-default | var(--semi-color-disabled-text) | 级联选择禁用态图标颜色 |
| $color-cascader_search-border-default | var(--semi-color-fill-0) | 级联选择搜索描边颜色 |
| $color-cascader_option_empty-text-default | var(--semi-color-disabled-text) | 级联选择空状态文字颜色 |
| $color-cascader_input-bg-default | transparent | 级联选择输入框背景颜色 |
| $color-cascader_input-border-default | none | 级联选择输入框描边颜色 |
| $color-cascader_select_highlight | var(--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-border | 1px | 级联选择触发器描边宽度 |
| $width-cascader_option_list-border | 1px | 级联选择各级菜单分割线宽度 |
| $width-cascader | 80px | 级联选择触发器最小宽度 |
| $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-border | 1px | 级联选择触搜索描边宽度 |
| $width-cascader-icon | 32px | 级联选择触发器图标尺寸 |
| $width-cascader-option-icon | 16px | 级联选择菜单项图标尺寸 |
| $width-cascader_option | 150px | 级联选择各级菜单宽度 |
| $height-cascader_option_list | 180px | 级联选择菜单高度 |
| $height-cascader_selection_tagInput_wrapper_small | 22px | 级联选择多选搜索时搜索框最小高度 - 小尺寸 |
| $height-cascader_selection_tagInput_wrapper_default | 30px | 级联选择多选搜索时搜索框最小高度 - 默认尺寸 |
| $height-cascader_selection_tagInput_wrapper_large | 38px | 级联选择多选搜索时搜索框最小高度 - 大尺寸 |
| $height-cascader_selection_wrapper_small | 22px | 级联选择单选搜索时搜索框高度 - 小尺寸 |
| $height-cascader_selection_wrapper | 30px | 级联选择单选搜索时搜索框高度 - 默认尺寸 |
| $height-cascader_selection_wrapper_large | 38px | 级联选择单选搜索时搜索框高度 - 大尺寸 |
| $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-paddingRight | 64px | 级联选择搜索结果菜单右侧内边距 |
| $spacing-cascader_empty_icon-marginRight | 8px | 级联选择搜索结果菜单 |
| $spacing-cacader_option_list-paddingY | 4px | 级联选择下拉菜单列表垂直内边距 |
| $spacing-cacader_option_list-paddingX | 0px | 级联选择下拉菜单列表水平内边距 |
| $spacing-cascader_search-paddingX | 12px | 级联选择搜索水平内边距 |
| $spacing-cascader_search-paddingY | 8px | 级联选择搜索垂直内边距 |
| $spacing-cascader_option_empty-paddingY | 8px | 级联选择空结果垂直内边距 |
| $spacing-cascader_option_empty-paddingX | 12px | 级联选择空结果水平内边距 |
| $spacing-cascader_option-paddingTop | 8px | 级联选择菜单项顶部内边距 |
| $spacing-cascader_option-paddingBottom | 8px | 级联选择菜单项底部内边距 |
| $spacing-cascader_option-paddingLeft | 12px | 级联选择菜单项左侧内边距 |
| $spacing-cascader_option-paddingRight | 16px | 级联选择菜单项右侧内边距 |