Demos
How to import
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
Basic Usage
Tabs supports four types of styles: line, button, card, and slash. By default, the first enabled tab is selected.
Tabs supports two declare ways, and the rendering process of the two is different:
- Pass the array of objects through
tabList, when usingtabList, only render the currently passed node each time - Or use
<TabPane>to explicitly pass in item by item. When using<TabPane>, all panels will be rendered by default. You can setkeepDOMtofalseto only render the current panel, and there will be no animation effect at this time.
The items of tabList hold data only (itemKey, tab, disabled, closable); for markup use renderTab and renderIcon of Tabs, which receive the item and its index. Without renderTab the tab text of the item is shown. The render functions are called once at each place the tab shows (the tab, a "more" menu item, the panel's name) and again only when the item's data or index changes.
Code
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<div>
<Tabs type="line">
<TabPane tab="文档" itemKey="1">
<h3>文档</h3>
<p style={{ 'line-height': '1.8' }}>Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
<p style={{ 'line-height': '1.8' }}>区别于其他的设计系统而言,Semi Design 以用户中心、内容优先、设计人性化为设计理念,具有以下优势:</p>
<ul>
<li>
<p>Semi Design 以内容优先进行设计。</p>
</li>
<li>
<p>更容易地自定义主题。</p>
</li>
<li>
<p>适用国际化场景。</p>
</li>
<li>
<p>效率场景加入人性化关怀。</p>
</li>
</ul>
</TabPane>
<TabPane tab="快速起步" itemKey="2">
<h3>快速起步</h3>
<pre
style={{
margin: '24px 0',
padding: '20px',
border: 'none',
'white-space': 'normal',
'border-radius': 'var(--semi-border-radius-medium)',
color: 'var(--semi-color-text-1)',
'background-color': 'var(--semi-color-fill-0)',
}}
>
<code>yarn add @fibrous/semi-ui-solid</code>
</pre>
</TabPane>
<TabPane tab="帮助" itemKey="3">
<h3>帮助</h3>
<p style={{ 'line-height': '1.8', color: 'var(--semi-color-text-0)', 'font-weight': '600' }}>Q:有新组件需求、或者现有组件feature不能满足业务需求?</p>
<p style={{ 'line-height': '1.8', color: 'var(--semi-color-text-1)' }}>右上角问题反馈,提交issue,label选择Feature Request / New Component Request 我们会高优处理这些需求。</p>
<p style={{ 'line-height': '1.8', color: 'var(--semi-color-text-0)', 'font-weight': '600' }}>Q:对组件的使用有疑惑?</p>
<p style={{ 'line-height': '1.8', color: 'var(--semi-color-text-1)' }}>欢迎进我们的客服lark群进行咨询提问。</p>
</TabPane>
</Tabs>
</div>
);
}Code
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<Tabs type="button">
<TabPane tab="文档" itemKey="1">
文档
</TabPane>
<TabPane tab="快速起步" itemKey="2">
快速起步
</TabPane>
<TabPane tab="帮助" itemKey="3">
帮助
</TabPane>
</Tabs>
);
}Code
import { createSignal } from 'solid-js';
import { Tabs } from '@fibrous/semi-ui-solid';
const contentList = ['文档', '快速起步', '帮助'];
const tabList = [
{ tab: '文档', itemKey: '1' },
{ tab: '快速起步', itemKey: '2' },
{ tab: '帮助', itemKey: '3' },
];
export default function TabsDemo() {
const [key, setKey] = createSignal('1');
return (
<Tabs type="card" tabList={tabList} onChange={setKey}>
<div>{contentList[Number(key()) - 1]}</div>
</Tabs>
);
}Code
import { createSignal } from 'solid-js';
import { Tabs } from '@fibrous/semi-ui-solid';
const contentList = ['文档', '快速起步', '帮助'];
const tabList = [
{ tab: '文档', itemKey: '1' },
{ tab: '快速起步', itemKey: '2' },
{ tab: '帮助', itemKey: '3' },
];
export default function TabsDemo() {
const [key, setKey] = createSignal('1');
return (
<Tabs type="slash" tabList={tabList} onChange={setKey}>
<div>{contentList[Number(key()) - 1]}</div>
</Tabs>
);
}With Icon
Code
import { IconFile, IconGlobe, IconHelpCircle } from '@fibrous/semi-icons-solid';
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<Tabs>
<TabPane
tab={
<span>
<IconFile />
文档
</span>
}
itemKey="1"
>
文档
</TabPane>
<TabPane
tab={
<span>
<IconGlobe />
快速起步
</span>
}
itemKey="2"
>
快速起步
</TabPane>
<TabPane
tab={
<span>
<IconHelpCircle />
帮助
</span>
}
itemKey="3"
>
帮助
</TabPane>
</Tabs>
);
}More with Dropdown
Supports merging redundant tabs into a more drop-down menu. Just pass in a number for more. The number represents the number of tabs included in the drop-down menu.
Code
import { For } from 'solid-js';
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
export default function TabsDemo() {
return (
<Tabs more={4} style={{ width: '60%', margin: '20px' }} type="card">
<For each={indexes}>
{(i) => (
<TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`}>
Content of card tab {i}
</TabPane>
)}
</For>
</Tabs>
);
}Advanced configuration is also supported, passing the object to more, and it can be passed in
count: Represents the number of Tabs in the income drop-down menurender: Customize the rendering function of the trigger. The returned JSX will be rendered as the trigger of the drop-down menu.dropdownProps: Dropdown props, passed through to the drop-down menu
Code
import { For } from 'solid-js';
import { Tabs, TabPane, Button } from '@fibrous/semi-ui-solid';
const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
export default function TabsDemo() {
return (
<Tabs
more={{
count: 4,
render: () => <Button type="tertiary">Click to show More</Button>,
dropdownProps: { trigger: 'click', position: 'bottomRight' },
}}
style={{ width: '60%', margin: '20px' }}
type="card"
>
<For each={indexes}>
{(i) => (
<TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`}>
Content of card tab {i}
</TabPane>
)}
</For>
</Tabs>
);
}Vertical mode
When type is line, card, or button, horizontal and vertical modes are supported, tabPosition='left|top',default is top. When type is slash, only horizontal mode is supported.
Code
import { createSignal } from 'solid-js';
import { IconFile, IconGlobe, IconHelpCircle } from '@fibrous/semi-icons-solid';
import { Tabs, TabPane, RadioGroup, Radio, type TabType, type RadioChangeEvent } from '@fibrous/semi-ui-solid';
const contentStyle = { padding: '0 24px' };
const paragraphStyle = { 'line-height': '1.8' };
const questionStyle = { 'line-height': '1.8', color: 'var(--semi-color-text-0)', 'font-weight': '600' };
const answerStyle = { 'line-height': '1.8', color: 'var(--semi-color-text-1)' };
const codeStyle = {
margin: '24px 0',
padding: '20px',
border: 'none',
'white-space': 'normal',
'border-radius': '6px',
color: 'var(--semi-color-text-1)',
'background-color': 'var(--semi-color-fill-0)',
};
export default function TabsDemo() {
const [type, setType] = createSignal<TabType>('line');
const onSelect = (e: RadioChangeEvent) => {
setType(e.target.value);
};
return (
<>
<RadioGroup
onChange={onSelect}
value={type()}
type="button"
style={{
display: 'flex',
width: '200px',
'justify-content': 'center',
}}
>
<Radio value="line">Line</Radio>
<Radio value="card">Card</Radio>
<Radio value="button">Button</Radio>
</RadioGroup>
<br />
<br />
<Tabs tabPosition="left" type={type()}>
<TabPane
tab={
<span>
<IconFile />
文档
</span>
}
itemKey="1"
>
<div style={contentStyle}>
<h3>文档</h3>
<p style={paragraphStyle}>Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。</p>
<p style={paragraphStyle}>区别于其他的设计系统而言,Semi Design 以用户中心、内容优先、设计人性化为设计理念,具有以下优势:</p>
</div>
</TabPane>
<TabPane
tab={
<span>
<IconGlobe />
快速起步
</span>
}
itemKey="2"
>
<div style={contentStyle}>
<h3>快速起步</h3>
<pre style={codeStyle}>
<code>yarn add @fibrous/semi-ui-solid</code>
</pre>
</div>
</TabPane>
<TabPane
tab={
<span>
<IconHelpCircle />
帮助
</span>
}
itemKey="3"
>
<div style={contentStyle}>
<h3>帮助</h3>
<p style={questionStyle}>Q:有新组件需求、或者现有组件feature不能满足业务需求?</p>
<p style={answerStyle}>右上角问题反馈,提交issue,label选择Feature Request / New Component Request 我们会高优处理这些需求。</p>
<p style={questionStyle}>Q:对组件的使用有疑惑?</p>
<p style={answerStyle}>欢迎进我们的客服lark群进行咨询提问。</p>
</div>
</TabPane>
</Tabs>
</>
);
}Scrollable Tabs
You could use collapsible for a scrollable tabs with dropdown menu. Horizontal mode only.
Code
import { For } from 'solid-js';
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
export default function TabsDemo() {
return (
<Tabs style={{ width: '60%', margin: '20px' }} type="card" collapsible>
<For each={indexes}>
{(i) => (
<TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`}>
Content of card tab {i}
</TabPane>
)}
</For>
</Tabs>
);
}Modify the scrolling rendering Arrow
renderArrow modifies the arrows in collapsible mode. Its arguments are the overflowed items, the position pos, the click function handleArrowClick and defaultNode, which creates the default arrow: call defaultNode() where it is inserted. items are plain data; with TabPane children they have no tab, so use tabList when you need the tab text.
Code
import { createSignal, For } from 'solid-js';
import { Tabs, TabPane, Dropdown, type TabsProps } from '@fibrous/semi-ui-solid';
const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const arrowStyle = {
width: '32px',
height: '32px',
margin: '0 12px',
display: 'flex',
'justify-content': 'center',
'align-items': 'center',
padding: '0',
border: 'none',
'border-radius': '100%',
background: 'var(--semi-grey-1)',
color: 'var(--semi-color-text)',
'font-family': 'inherit',
'font-size': 'inherit',
cursor: 'pointer',
};
export default function TabsDemo() {
const [activeKey, setActiveKey] = createSignal('Tab-0');
const renderArrow: TabsProps['renderArrow'] = (items, pos, handleArrowClick) => (
<Dropdown
render={
<Dropdown.Menu>
<For each={items}>{(item) => <Dropdown.Item onClick={() => setActiveKey(item.itemKey)}>{item.itemKey}</Dropdown.Item>}</For>
</Dropdown.Menu>
}
>
{(trigger) => (
<button type="button" {...trigger()} style={arrowStyle} onClick={handleArrowClick}>
{pos === 'start' ? '←' : '→'}
</button>
)}
</Dropdown>
);
return (
<Tabs renderArrow={renderArrow} style={{ width: '50%', margin: '20px' }} activeKey={activeKey()} type="card" collapsible onChange={setActiveKey}>
<For each={indexes}>
{(i) => (
<TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`}>
Content of card tab {i}
</TabPane>
)}
</For>
</Tabs>
);
}Modify Arrow rendering position
Use arrowPosition to modify the overflow indicator position, optional start both end
Code
import { For } from 'solid-js';
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
const indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const positions = ['start', 'both', 'end'] as const;
export default function TabsDemo() {
return (
<For each={positions}>
{(position) => (
<Tabs style={{ width: '60%', margin: '20px' }} type="card" collapsible arrowPosition={position}>
<For each={indexes}>
{(i) => (
<TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`}>
Content of card tab {i}
</TabPane>
)}
</For>
</Tabs>
)}
</For>
);
}Auto Overflow Detection
Set collapsible="auto" to enable automatic overflow detection. The component will automatically detect whether tabs overflow the container:
- When tabs exceed the container width or wrap to multiple lines, it automatically enables collapse mode (shows left/right arrows)
- When the container width increases or tab count decreases and all tabs can be fully displayed, it automatically exits collapse mode
This feature eliminates the need for developers to manually determine whether folding is needed, making it especially suitable for responsive layouts.
collapsible="auto" - 自动检测溢出
固定宽度 400px,测试较窄容器
Code
import { createSignal, For } from 'solid-js';
import { Tabs, TabPane, Button } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
const [count, setCount] = createSignal(8);
const indexes = () => Array.from({ length: count() }, (_, i) => i);
// Nodes can be placed once, so each Tabs gets its own copy
const panes = () => (
<For each={indexes()}>
{(i) => (
<TabPane tab={`Tab-${i + 1}`} itemKey={`${i}`}>
Content of Tab {i + 1}
</TabPane>
)}
</For>
);
return (
<div>
<div style={{ 'margin-bottom': '12px' }}>
<Button onClick={() => setCount(Math.max(3, count() - 2))} style={{ 'margin-right': '8px' }}>
减少 Tab
</Button>
<Button onClick={() => setCount(Math.min(15, count() + 2))}>增加 Tab</Button>
<span style={{ 'margin-left': '12px' }}>当前 Tab 数量: {count()}</span>
</div>
<div style={{ border: '1px solid var(--semi-color-border)', padding: '12px', 'margin-bottom': '16px' }}>
<p style={{ 'margin-bottom': '8px', color: 'var(--semi-color-text-2)', 'font-size': '12px' }}>collapsible="auto" - 自动检测溢出</p>
<Tabs type="card" collapsible="auto">
{panes()}
</Tabs>
</div>
<div style={{ border: '1px solid var(--semi-color-border)', padding: '12px', 'max-width': '400px' }}>
<p style={{ 'margin-bottom': '8px', color: 'var(--semi-color-text-2)', 'font-size': '12px' }}>固定宽度 400px,测试较窄容器</p>
<Tabs type="card" collapsible="auto">
{panes()}
</Tabs>
</div>
</div>
);
}Disable
Disable one tab.
Code
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<Tabs defaultActiveKey="1">
<TabPane tab="文档" itemKey="1">
文档
</TabPane>
<TabPane tab="快速起步" itemKey="2" disabled>
快速起步
</TabPane>
<TabPane tab="帮助" itemKey="3">
帮助
</TabPane>
</Tabs>
);
}Extra Content
Use tabBarExtraContent to add extra content on the right side of tabBar.
Code
import { Tabs, TabPane, Button } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<Tabs
defaultActiveKey="1"
tabBarExtraContent={
<Button
onClick={() => {
alert('you have clicked me!');
}}
>
Extra Action
</Button>
}
>
<TabPane tab="文档" itemKey="1">
文档
</TabPane>
<TabPane tab="快速起步" itemKey="2">
快速起步
</TabPane>
<TabPane tab="帮助" itemKey="3">
帮助
</TabPane>
</Tabs>
);
}Custom Render
Use renderTabBar to customize tabBar render behavior; the same function is called once. tabBarProps is live, so read its fields inside the returned JSX; DefaultTabBar is the default tab bar component, rendered with <DefaultTabBar {...tabBarProps} />.
When renderTabBar replaces the bar entirely, the aria-label and aria-labelledby of Tabs are in tabBarProps and do not reach your own element automatically: put the accessible name on your own role="tablist" element. With TabPane children, the entries of tabBarProps.list are plain data without tab; use tabList when you need the tab text.
Code
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
return (
<Tabs
defaultActiveKey="1"
renderTabBar={(tabBarProps, DefaultTabBar) => (
<div class="tab-bar-box">
这是二次封装的Tab Bar,当前ActiveKey:{tabBarProps.activeKey}
<DefaultTabBar {...tabBarProps} />
</div>
)}
>
<TabPane tab="文档" itemKey="1">
文档
</TabPane>
<TabPane tab="快速起步" itemKey="2">
快速起步
</TabPane>
<TabPane tab="帮助" itemKey="3">
帮助
</TabPane>
</Tabs>
);
}Drag and Drop Reordering
You can implement drag-and-drop tab reordering using renderTabBar combined with third-party drag libraries like @dnd-kit: render the tabs with Tabs.TabItem inside your own role="tablist" element. A TabItem does not select its tab on click by itself; update activeKey in onClick.
Code
import { createSignal, For } from 'solid-js';
import { Accessibility } from '@dnd-kit/dom';
import { arrayMove } from '@dnd-kit/helpers';
import { DragDropProvider } from '@dnd-kit/solid';
import { isSortable, useSortable } from '@dnd-kit/solid/sortable';
import { Tabs, TabPane, type PlainTab, type TabsProps, type TabItemProps } from '@fibrous/semi-ui-solid';
// The Provider drops the Accessibility plugin so TabItem keeps role="tab" and roving tabIndex, without aria-pressed
interface SortableTabItemProps extends Pick<TabItemProps, 'selected' | 'size' | 'type' | 'tabPosition' | 'onClick'> {
item: PlainTab;
index: number;
}
function SortableTabItem(props: SortableTabItemProps) {
const { ref, isDragging } = useSortable({
get id() {
return props.item.itemKey;
},
get index() {
return props.index;
},
get disabled() {
return props.item.disabled;
},
});
return (
<Tabs.TabItem
ref={(node) => ref(node)}
style={{ cursor: props.item.disabled ? 'not-allowed' : 'grab', opacity: isDragging() ? 0.5 : 1, display: 'inline-flex' }}
itemKey={props.item.itemKey}
tab={props.item.tab}
disabled={props.item.disabled}
selected={props.selected}
size={props.size}
type={props.type}
tabPosition={props.tabPosition}
onClick={props.onClick}
/>
);
}
export default function TabsDemo() {
const [activeKey, setActiveKey] = createSignal('1');
const [items, setItems] = createSignal([
{ itemKey: '1', tab: '文档', content: '文档内容' },
{ itemKey: '2', tab: '表格', content: '表格内容' },
{ itemKey: '3', tab: '幻灯片', content: '幻灯片内容' },
{ itemKey: '4', tab: '表单', content: '表单内容' },
]);
const renderTabBar: TabsProps['renderTabBar'] = (tabBarProps) => (
<DragDropProvider
plugins={(defaults) => defaults.filter((plugin) => plugin !== Accessibility)}
onDragEnd={(event) => {
const { source } = event.operation;
if (!event.canceled && isSortable(source)) {
setItems((prev) => arrayMove(prev, source.initialIndex, source.index));
}
}}
>
<div class={`semi-tabs-bar semi-tabs-bar-${tabBarProps.type} semi-tabs-bar-${tabBarProps.tabPosition}`} role="tablist">
<For each={items()}>{(item, index) => <SortableTabItem item={item} index={index()} selected={activeKey() === item.itemKey} size={tabBarProps.size} type={tabBarProps.type} tabPosition={tabBarProps.tabPosition} onClick={(key) => setActiveKey(key)} />}</For>
</div>
</DragDropProvider>
);
return (
<Tabs type="line" activeKey={activeKey()} onChange={setActiveKey} renderTabBar={renderTabBar}>
<For each={items()}>
{(item) => (
<TabPane tab={item.tab} itemKey={item.itemKey}>
<div style={{ padding: '20px' }}>{item.content}</div>
</TabPane>
)}
</For>
</Tabs>
);
}Dynamic Update
You can add events to update tabBar dynamically.
Code
import { createSignal, For } from 'solid-js';
import { Tabs, TabPane, ButtonGroup, Button } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
let newTabIndex = 0;
const [panes, setPanes] = createSignal([
{ title: 'Tab 1', content: 'Content of Tab Pane 1', itemKey: '1' },
{ title: 'Tab 2', content: 'Content of Tab Pane 2', itemKey: '2' },
]);
const [activeKey, setActiveKey] = createSignal('1');
const add = () => {
const index = newTabIndex++;
setPanes([...panes(), { title: `New Tab ${index}`, content: 'New Tab Pane', itemKey: `newTab${index}` }]);
setActiveKey(`newTab${index}`);
};
const remove = () => {
if (panes().length > 1) {
const rest = panes().slice(0, -1);
setPanes(rest);
setActiveKey(rest[rest.length - 1].itemKey);
}
};
return (
<Tabs
activeKey={activeKey()}
onChange={setActiveKey}
tabBarExtraContent={
<ButtonGroup>
<Button onClick={add}>新增</Button>
<Button onClick={remove}>删除</Button>
</ButtonGroup>
}
>
<For each={panes()}>
{(pane) => (
<TabPane tab={pane.title} itemKey={pane.itemKey}>
{pane.content}
</TabPane>
)}
</For>
</Tabs>
);
}Closeable
Close a tab in the tab bar. All four tab styles support the close option; set closable on the tab to turn it on. A disabled tab cannot be closed. The caller removes the tab in the close callback.
Code
import { createSignal, For } from 'solid-js';
import { Tabs, TabPane } from '@fibrous/semi-ui-solid';
export default function TabsDemo() {
const [tabList, setTabList] = createSignal([
{ tab: '文档', itemKey: '1', text: '文档', closable: true },
{ tab: '快速起步', itemKey: '2', text: '快速起步', closable: true },
{ tab: '帮助', itemKey: '3', text: '帮助' },
]);
const close = (key: string) => {
setTabList(tabList().filter((t) => t.itemKey !== key));
};
return (
<Tabs type="card" defaultActiveKey="1" onTabClose={close}>
<For each={tabList()}>
{(t) => (
<TabPane closable={t.closable} tab={t.tab} itemKey={t.itemKey}>
{t.text}
</TabPane>
)}
</For>
</Tabs>
);
}API Reference
Tab
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | The itemKey of the active tab, controlled. null activates no tab and is still controlled; omitted or undefined means uncontrolled. | string | null | - |
| aria-label | Accessible name of the tab bar, placed on the role="tablist" element. | string | - |
| aria-labelledby | The id of the element that names the tab bar, placed on the role="tablist" element. | string | - |
| arrowPosition | Position of the scroll arrows in collapsible mode. | TabBarProps['arrowPosition'] | "both" |
| children | TabPane children, which must be direct children of Tabs; any other node renders as content in its original order. With tabList, TabPane children are ignored and the other nodes are the content of the active tab. | JSX.Element | - |
| collapsible | Whether to collapse overflowing tabs into a scrollable bar (horizontal mode only). 'auto' collapses when the tabs overflow or wrap and leaves the collapse once they fit. | boolean | 'auto' | false |
| contentStyle | Style of the outer content area, an object or a string. | JSX.CSSProperties | string | - |
| defaultActiveKey | The itemKey selected initially when uncontrolled. Defaults to the first enabled tab that has an itemKey. | string | - |
| dropdownProps | In collapsible mode, the Dropdown props passed to the start-side (start) and end-side (end) arrow menus. | TabsDropdownProps | - |
| keepDOM | With TabPane children, whether to render the DOM of inactive panels. When false only the active panel is rendered, without motion. | boolean | true |
| lazyRender | Lazy rendering: a panel renders its content only after it has been active, and keeps it afterwards. | boolean | false |
| more | Moves the last tabs into a More dropdown. A number is the count of tabs moved; in an object, count is that number, render returns a custom trigger and dropdownProps is passed to the dropdown. | TabsMore | - |
| onChange | Called when the active tab changes, with the itemKey of the new active tab. | Type(activeKey: string) => void | - |
| onTabClick | Called when a tab is clicked or activated with Enter or Space; the event is a KeyboardEvent for keyboard activation. | Type(activeKey: string, event: MouseEvent | KeyboardEvent) => void | - |
| onTabClose | Called when a tab is closed, with the itemKey of the closed tab. The caller removes the tab. | Type(tabKey: string) => void | - |
| onVisibleTabsChange | In collapsible mode, called when the visibility of the tabs changes, with a Map from itemKey to whether it is visible. | TabBarProps['onVisibleTabsChange'] | - |
| preventScroll | Whether focus calls inside the component stop the browser from scrolling the document to the newly focused element. | boolean | - |
| renderArrow | Replaces each scroll arrow in collapsible mode. The arguments are the scrolled-out items, the position pos, the click handler handleArrowClick and defaultNode, which creates the default arrow: call defaultNode() where it is inserted. items are plain data; with TabPane children they have no tab, so use tabList when you need the tab text. | TabBarProps['renderArrow'] | - |
| renderIcon | The icon of each tabList item, called with the item and its index. It is called once at each place the tab shows (the tab, a menu item) and again when the item's data or index changes. | TabRender | - |
| renderTab | The content of each tabList tab, called with the item and its index; without it the item's tab shows. It is called once at each place the tab shows (the tab, a menu item, the panel's name) and again when the item's data or index changes. | TabRender | - |
| renderTabBar | Replaces the whole tab bar; the same function is called once. tabBarProps is live, so read its fields inside the returned JSX; DefaultTabBar is the default tab bar component. | Type(tabBarProps: TabBarProps, DefaultTabBar: Component<TabBarProps>) => JSX.Element | - |
| showRestInDropdown | In collapsible mode, whether the collapsed tabs are listed in a dropdown menu. | boolean | true |
| size | Size of the tab bar; currently only the line type supports it. | TabSize | "large" |
| tabBarClassName | Class name of the tab bar. | string | - |
| tabBarExtraContent | Extra content of the tab bar, placed outside the tablist. | JSX.Element | - |
| tabBarStyle | Style of the tab bar, an object or a string. | JSX.CSSProperties | string | - |
| tabList | An array of tab data. Each item holds data only (itemKey, the tab text, disabled and closable); markup goes to renderTab and renderIcon. Any array, even an empty one, makes children the content of the active tab and takes precedence over TabPane children. | PlainTab[] | - |
| tabPaneMotion | Whether switching panels is animated. | boolean | true |
| tabPosition | Position of the tab bar: top is horizontal, left is vertical. | TabPosition | "top" |
| type | Style of the tab bar: line, card, button or slash. slash supports horizontal mode only. | TabType | "line" |
| visibleTabsStyle | In collapsible mode, the style of the whole scrolling area, an object or a string. | TabBarProps['visibleTabsStyle'] | - |
class, style, ref and other native attributes and events land on the root element; aria-label and aria-labelledby land on the role="tablist" element.
TabPane
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | Panel content. | JSX.Element | - |
| closable | Whether the tab can be closed; closing calls onTabClose of the Tabs. | boolean | - |
| disabled | Whether the tab is disabled. | boolean | - |
| icon | The icon of the tab in the bar, rendered by the Tabs. | JSX.Element | - |
| itemKey | Unique key of the tab, matched against activeKey. | string | - |
| tab | The tab content in the bar, rendered by the Tabs. | JSX.Element | - |
class, style, ref and native attributes and events (including id, role, aria-* and tabIndex) land on the role="tabpanel" panel element. The id, role, aria-hidden and the aria-labelledby that pairs it with its tab are generated by the component and are not overridden; tabIndex defaults to 0 and can be overridden.
TabItem
A single tab inside a custom tab bar (renderTabBar), used as Tabs.TabItem. Inside a Tabs it uses the keyboard and close handling of the Tabs, but clicking it does not select its tab by itself: update activeKey in onClick.
Props
| Property | Description | Type | Default |
|---|---|---|---|
| closable | Whether to show the close button. | boolean | - |
| deleteTabItem | Handler for the close button, with the itemKey and the event. Inside a Tabs it defaults to the Tabs' own, which calls onTabClose. | TabBarProps['deleteTabItem'] | - |
| disabled | Whether the tab is disabled; clicks and the close button have no effect, while the close button still shows. | boolean | - |
| handleKeyDown | Keyboard handler, with the event, the itemKey and closable. Inside a Tabs it defaults to the Tabs' own, which handles the arrow keys, Home, End, Enter, Space and Delete. | TabBarProps['handleKeyDown'] | - |
| icon | Tab icon; without content no icon is rendered. | JSX.Element | - |
| itemKey | Unique key of the tab. | string | - |
| onClick | Called when the tab is clicked, with the itemKey and the event; not called while disabled. TabItem does not select the tab itself: update the activeKey of the Tabs in the callback. | Type(itemKey: string, event: MouseEvent) => void | - |
| selected | Whether the tab is selected. | boolean | - |
| size | Size; small and medium add the matching class name. | TabSize | - |
| tab | Tab content. | JSX.Element | - |
| tabPosition | Position; defaults to the tabPosition of the enclosing Tabs. | TabPosition | - |
| type | Style; defaults to the type of the enclosing Tabs. | TabType | - |
class, style, ref and native attributes and events land on the root element; role, id, aria-selected, aria-disabled, aria-controls and tabIndex are managed by the component and are not overridden.
Accessibility
ARIA
- About role
- The element in TabBar that holds only the Tabs has a role of
tablist; the scroll arrows, the "more" trigger andtabBarExtraContentsit outside it - Tab in TabBar has a role of
tab - TabPane has a role of
tabpanel
- The element in TabBar that holds only the Tabs has a role of
- aria-label, aria-labelledby: When passed to Tabs they land on the
role="tablist"element and name the tab bar - aria-orientation: Indicates TabBar's orientation, can be
verticalorhorizontal. When tabPosition isleft,aria-orientation will bevertical, when tabPosition istop, aria-orientation will behorizontal. - aria-disabled: When TabPane is disabled, the related Tab's aria-disabled will be set to true.
- aria-selected: Indicates whether the Tab is selected.
- aria-controls: Indicates the TabPane controlled by the Tab; when only the active tab's panel is in the document (with
tabList, or withkeepDOMset to false), only the selected Tab carries it - aria-labelledby: Indicates the element labels the TabPane
Keyboard and Focus
WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/
- Tabs can be given focus, except for disabled tabs
- Keyboard users can use the
Tabkey to move the focus to the tab panel of the selected tab element - Use
left and right arrowsto toggle options when focus is on a tab element in a horizontal tab list (the two arrows swap in an RTL page) - Use
up and down arrowsto toggle options when focus is on a tab element in a vertical tab list - When the focus is on an inactive tab element in the tab list, the
SpaceorEnterkeys can be used to activate the tab - When keyboard users want to focus directly on the last tab element in the tab list:
- Mac users:
fn+right arrow - Windows users:
End
- Mac users:
- When keyboard users want to focus directly on the first tab element in the tab list:
- Mac users:
fn+left arrow - Windows users:
Home
- Mac users:
- With
more, the default "More" trigger is a nativebutton: focus it withTab, open the dropdown withEnterorSpace; it carriesaria-haspopup="menu"andaria-expanded - When a tab is allowed to be deleted:
- Users can use the
DeleteorBackspacekey to delete a tab, except a disabled one - After deletion, the focus is transferred to the next element of the deleted tab element; if the deleted element has no subsequent element, it is transferred to the previous element
- Users can use the
Design Token
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-tabs_tab_line-border | var(--semi-transition_duration-none) | 线条式标签页标示线-边框-动画持续时间 |
| $transition_function-tabs_tab_line-border | var(--semi-transition_function-easeIn) | 线条式标签页标示线-边框-过渡曲线 |
| $transition_delay-tabs_tab_line-border | var(--semi-transition_delay-none) | 线条式标签页标示线-边框-延迟时间 |
| $transition_duration-tabs_tab_line-text | var(--semi-transition_duration-none) | 线条式标签页-文字-动画持续时间 |
| $transition_function-tabs_tab_line-text | var(--semi-transition_function-easeIn) | 线条式标签页-文字-过渡曲线 |
| $transition_delay-tabs_tab_line-text | var(--semi-transition_delay-none) | 线条式标签页-文字-延迟时间 |
| $transition_duration-tabs_tab_button-bg | var(--semi-transition_duration-none) | 按钮式标签页-背景色-动画持续时间 |
| $transition_function-tabs_tab_button-bg | var(--semi-transition_function-easeIn) | 按钮式标签页-背景色-过渡曲线 |
| $transition_delay-tabs_tab_button-bg | var(--semi-transition_delay-none) | 按钮式标签页-背景色-延迟时间 |
| $transition_duration-tabs_tab_card-bg | var(--semi-transition_duration-none) | 卡片式标签页文字-背景色-动画持续时间 |
| $transition_function-tabs_tab_card-bg | var(--semi-transition_function-easeIn) | 卡片式标签页文字-背景色-过渡曲线 |
| $transition_delay-tabs_tab_card-bg | var(--semi-transition_delay-none) | 卡片式标签页文字-背景色-延迟时间 |
| $transform_scale-tabs_tab_line-item | var(--semi-transform_scale-none) | 线条式标签页item 放大 |
| $transform_scale-tabs_tab_button-item | var(--semi-transform_scale-none) | 按钮式标签页item 放大 |
| $transform_scale-tabs_tab_card-item | var(--semi-transform_scale-none) | 线条式标签页item 放大 |
| $animation_transform_translateX-tabs_tabPanel-leftShow | 60px | 左侧展示时,tabPanel的水平偏移值 |
| $animation_transform_translateX-tabs_tabPanel-rightShow | -60px | 右侧展示时,tabPanel的水平偏移值 |
| $animation_opacity-tabs_tabPanel_show | 0 | tabPanel的动画开始时透明度 |
| $animation_duration-tabs_tabPanel-show | 200ms | tabPanel的展示动画持续时间 |
| $animation_function-tabs_tabPanel-show | ease-in-out | tabPanel的展示动画曲线 |
| $animation_delay-tabs_tabPanel-show | 0s | tabPanel的展示动画延迟时间 |
| $color-tabs_tab_line_default-border-default | var(--semi-color-border) | 线条式页签轨道颜色 - 默认 |
| $color-tabs_tab_line_default-text-default | var(--semi-color-text-2) | 线条式页签文本颜色 - 默认 |
| $color-tabs_tab_line_default-text-hover | var(--semi-color-text-0) | 线条式页签文本颜色 - 悬浮 |
| $color-tabs_tab_line_default-border-hover | var(--semi-color-fill-0) | 线条式页签标示线颜色 - 悬浮 |
| $color-tabs_tab_line_default-text-active | var(--semi-color-text-0) | 线条式页签文本颜色 - 按下 |
| $color-tabs_tab_line_default-border-active | var(--semi-color-fill-1) | 线条式页签标示线颜色 - 按下 |
| $color-tabs_tab_line_selected-text-default | var(--semi-color-text-0) | 线条式页签文本颜色 - 选中 |
| $color-tabs_tab_line_indicator_selected-icon-default | var(--semi-color-primary) | 线条式页签标示线颜色 - 选中 |
| $color-tabs_tab_line_vertical_selected-bg-default | var(--semi-color-primary-light-default) | 垂直线条式页签背景颜色 - 选中 |
| $color-tabs_tab_line_vertical-bg-hover | var(--semi-color-fill-0) | 垂直线条式页签背景颜色 - 悬浮 |
| $color-tabs_tab_line_vertical-bg-active | var(--semi-color-fill-1) | 垂直线条式页签背景颜色 - 按下 |
| $color-tabs_tab_line_disabled-text-default | var(--semi-color-disabled-text) | 禁用线条式页签文本颜色 - 默认 |
| $color-tabs_tab_line_disabled-text-hover | var(--semi-color-disabled-text) | 禁用线条式页签文本颜色 - 悬浮 |
| $color-tabs_tab_card_default-border-default | var(--semi-color-border) | 卡片式页签容器底部长线颜色 - 默认 |
| $color-tabs_tab_button_selected-bg-default | var(--semi-color-primary-light-default) | 按钮式页签背景颜色 - 选中 |
| $color-tabs_tab_button_selected-text-default | var(--semi-color-primary) | 按钮式页签文本颜色 - 选中 |
| $color-tabs_tab_button-text-default | var(--semi-color-text-2) | 按钮式页签文本颜色 - 默认 |
| $color-tabs_tab_button-bg-hover | var(--semi-color-fill-0) | 按钮式页签背景颜色 - 悬浮 |
| $color-tabs_tab_button-bg-active | var(--semi-color-fill-1) | 按钮式页签背景颜色 - 按下 |
| $color-tabs_tab_card_indicator_selected-icon-default | var(--semi-color-border) | 卡片式页签选中页签描边 |
| $color-tabs_tab_card-bg-hover | var(--semi-color-fill-0) | 卡片式页签背景颜色 - 悬浮 |
| $color-tabs_tab_card_selected-bg-default | var(--semi-color-bg-1) | 卡片式页签背景颜色 - 选中 |
| $color-tabs_tab_card-bg-active | var(--semi-color-fill-1) | 卡片式页签背景颜色 -按下 |
| $color-tabs_tab-icon-default | var(--semi-color-text-2) | 页签图标颜色 - 默认 |
| $color-tabs_tab-icon-hover | var(--semi-color-text-0) | 页签图标颜色 - 悬浮 |
| $color-tabs_tab-icon-active | var(--semi-color-text-0) | 页签图标颜色 - 按下 |
| $color-tabs_tab_selected-icon-default | var(--semi-color-primary) | 页签图标颜色 - 选中 |
| $color-tabs_tab-outline-focus | $color-focus-ring | 页签轮廓 - 聚焦 |
| $color-tabs_tab-pane-text-default | var(--semi-color-text-0) | 标签页内容文本颜色 - 默认 |
| $color-tabs_tab-pane_arrow-text-default | var(--semi-color-primary) | 滚动折叠箭头颜色 - 默认 |
| $color-tabs_tab-pane_arrow-border-default | transparent | 滚动折叠箭头边框颜色 - 默认 |
| $color-tabs_tab-pane_arrow-bg-default | transparent | 滚动折叠箭头背景色 - 默认 |
| $color-tabs_tab-pane_arrow-text-hover | var(--semi-color-primary) | 滚动折叠箭头颜色 - 悬浮 |
| $color-tabs_tab-pane_arrow-border-hover | transparent | 滚动折叠箭头边框颜色 - 悬浮 |
| $color-tabs_tab-pane_arrow-text-active | var(--semi-color-primary) | 滚动折叠箭头颜色 - 按下 |
| $color-tabs_tab-pane_arrow-border-active | transparent | 滚动折叠箭头边框颜色 - 按下 |
| $color-tabs_tab-pane_arrow_disabled-bg-default | transparent | |
| $color-tabs_tab-pane_arrow_disabled-bg-hover | transparent | |
| $color-tabs_tab-pane_arrow_disabled-text-default | var(--semi-color-disabled-text) | |
| $color-tabs_tab-pane_arrow_disabled-text-hover | var(--semi-color-disabled-text) | |
| $color-tabs_tab_slash_line | var(--semi-color-text-2) | 斜线式页签分割线颜色 |
| $font-tabs_tab-fontWeight | $font-weight-regular | 页签文本字重 - 默认 |
| $font-tabs_tab_active-fontWeight | $font-weight-bold | 页签文本字重 - 选中 |
| $height-tabs_overflow_list | 300px | 页签折叠收起菜单高度 |
| $width-tabs_bar_line-border | $border-thickness-control | 线条式页签底部分割线宽度 |
| $width-tabs_bar_line_tab-border | 2px | 页签标示线宽度 |
| $width-tabs_bar_card-border | $border-thickness-control | 卡片式页签容器底部长线宽度 |
| $width-tabs-outline | $width-focus-ring | 聚焦轮廓宽度 |
| $width-tabs-outline-offset | -2px | 聚焦轮廓偏移宽度 |
| $width-tabs_bar_line-outline-offset | -1px | 线条式页签聚焦轮廓偏移宽度 |
| $width-tabs_tab-pane_arrow-border | 0px | 滚动折叠箭头边框宽度 |
| $width-tabs_tab_slash_line | 8px | 斜线式页签分割线宽度 |
| $height-tabs_tab_slash_line | 14px | 斜线式页签分割线高度 |
| $height-tabs_bar_extra_large | 50px | 大尺寸页签高度 |
| $font-tabs_bar_extra_large-lineHeight | $height-tabs_bar_extra_large | 大尺寸页签文字行高 |
| $height-tabs_bar_extra_small | 36px | 小尺寸页签高度 |
| $font-tabs_bar_extra_small-lineHeight | $height-tabs_bar_extra_small | 小尺寸页签文字行高 |
| $spacing-tabs_tab-more_trigger_icon-marginLeft | 8px | |
| $spacing-tabs_tab-more_trigger_icon-marginRight | 8px | |
| $spacing-tabs_tab-more_trigger_icon-marginTop | 0px | |
| $spacing-tabs_tab-more_trigger_icon-marginBottom | 0px | |
| $spacing-tabs_tab-pane_arrow | 8px | 滚动折叠箭头内边距 |
| $spacing-tabs_bar_extra-paddingY | 0px | 附加操作垂直内边距 |
| $spacing-tabs_bar_extra-paddingX | 5px | 附加操作水平内边距 |
| $spacing-tabs_tab_icon-marginRight | $spacing-tight | 附加操作垂直内边距 |
| $spacing-tabs_tab_icon-top | 3px | 页签图标顶部位置 |
| $spacing-tabs_overflow_icon-marginRight | 4px | 页签折叠切换按钮右侧外边距 |
| $spacing-tabs_overflow_icon-marginLeft | 4px | 页签折叠切换按钮左侧外边距 |
| $spacing-tabs_bar_line_tab-paddingTop | $spacing-base | 线条式页签 |
| $spacing-tabs_bar_line_tab-paddingRight | $spacing-extra-tight | 线条式页签右侧内边距 |
| $spacing-tabs_bar_line_tab-paddingBottom | $spacing-base - $width-tabs_bar_line_tab-border | 线条式页签底部内边距 |
| $spacing-tabs_bar_line_tab-paddingLeft | $spacing-extra-tight | 线条式页签左侧内边距 |
| $spacing-tabs_bar_line_tab-marginRight | $spacing-loose | 线条式页签右侧外边距 |
| $spacing-tabs_bar_line_tab_small-paddingTop | $spacing-tight | 小尺寸线条式页签顶部内边距 |
| $spacing-tabs_bar_line_tab_small-paddingRight | $spacing-extra-tight | 小尺寸线条式页签右侧内边距 |
| $spacing-tabs_bar_line_tab_small-paddingBottom | $spacing-tight - $width-tabs_bar_line_tab-border | 小尺寸线条式页签底部内边距 |
| $spacing-tabs_bar_line_tab_small-paddingLeft | $spacing-extra-tight | 小尺寸线条式页签左侧内边距 |
| $spacing-tabs_bar_line_tab_medium-paddingTop | $spacing-base-tight | 中等尺寸线条式页签顶部内边距 |
| $spacing-tabs_bar_line_tab_medium-paddingRight | $spacing-extra-tight | 中等尺寸线条式页签右侧内边距 |
| $spacing-tabs_bar_line_tab_medium-paddingBottom | $spacing-base-tight - $width-tabs_bar_line_tab-border | 中等尺寸线条式页签底部内边距 |
| $spacing-tabs_bar_line_tab_medium-paddingLeft | $spacing-extra-tight | 中等尺寸线条式页签左侧内边距 |
| $spacing-tabs_bar_line_tab_left-padding | 12px | 垂直线条式页签左侧内边距 |
| $spacing-tabs_bar_line_tab_left_small-padding | $spacing-tight - 2px | 小尺寸垂直线条式页签左侧内边距 |
| $spacing-tabs_bar_line_tab_left_medium-padding | $spacing-base-tight - 2px | 中等尺寸垂直线条式页签左侧内边距 |
| $spacing-tabs_bar_slash_tab-paddingY | 12px | 斜线式页签上下内边距 |
| $spacing-tabs_bar_slash_tab-paddingX | 0px | 斜线式页签水平内边距 |
| $spacing-tabs_bar_slash-marginRight | 16px | 斜线式页签右侧外边距 |
| $spacing-tabs_bar_slash_line_marginLeft | 16px | 斜线式页签斜线左侧外边距 |
| $spacing-tabs_bar_slash_line_marginTop | 3px | 斜线式页签斜线顶部外边距 |
| $spacing-tabs_bar_slash_line_marginBottom | 3px | 斜线式页签斜线底部外边距 |
| $spacing-tabs_content-paddingY | 5px | 页签内容区垂直方向内边距 |
| $spacing-tabs_content-paddingX | 0 | 页签内容区水平方向内边距 |
| $spacing-tabs_content_left-paddingX | 5px | 垂直页签内容区垂直方向内边距 |
| $spacing-tabs_content_left-paddingY | 0 | 垂直页签内容区水平方向内边距 |
| $spacing-tabs_bar_card_tab-marginRight | $spacing-tight | 卡片式页签右侧外边距 |
| $spacing-tabs_bar_card_tab_active-paddingTop | $spacing-tight | 卡片式选中页签顶部内边距 |
| $spacing-tabs_bar_card_tab_active-paddingBottom | $spacing-tight - 1px | 卡片式选中页签底部内边距 |
| $spacing-tabs_bar_card_tab_active-paddingLeft | $spacing-base-tight | 卡片式选中页签左侧内边距 |
| $spacing-tabs_bar_card_tab_active-paddingRight | $spacing-base-tight | 卡片式选中页签右侧内边距 |
| $spacing-tabs_bar_card_tab_left-marginBottom | $spacing-tight | 垂直卡片式页签底部外边距 |
| $spacing-tabs_bar_card_tab_left_active-paddingY | $spacing-tight | 垂直卡片式选中页签垂直内边距 |
| $spacing-tabs_bar_card_tab_left_active-paddingX | $spacing-base-tight | 垂直卡片式选中页签水平内边距 |
| $spacing-tabs_bar_card_tab-paddingY | $spacing-tight | 卡片式选中页签垂直内边距 |
| $spacing-tabs_bar_card_tab-paddingX | $spacing-base-tight | 卡片式选中页签水平内边距 |
| $spacing-tabs_bar_button_tab_left-marginBottom | $spacing-tight | 垂直按钮式页签底部外边距 |
| $spacing-tabs_bar_button_tab-marginRight | $spacing-tight | 按钮式页签右侧外边距 |
| $spacing-tabs_bar_button_tab-paddingY | $spacing-tight | 按钮式页签垂直内边距 |
| $spacing-tabs_bar_button_tab-paddingX | $spacing-base-tight | 按钮式页签水平内边距 |
| $radius-tabs_tab_card | var(--semi-border-radius-small) var(--semi-border-radius-small) 0 0 | 卡片式页签四向圆角 |
| $radius-tabs_tab_card_left | var(--semi-border-radius-small) 0 0 var(--semi-border-radius-small) | 垂直卡片式页签四向圆角 |
| $radius-tabs_tab_button | var(--semi-border-radius-small) | 按钮式页签圆角 |
| $font-tabs_bar_large-fontSize | $font-size-regular | 大号模式 字体大小 |
| $font-tabs_bar_small-fontSize | $font-size-regular | 小号模式 字体大小 |
| $font-tabs_bar_medium-fontSize | $font-size-regular | 中等模式 字体大小 |
Content Guidelines
- Label copy needs to explain the label content accurately and clearly
- Use short, easily distinguishable labels
- try to stay within one word
FAQ
-
Why typography with ellipses in Tabs doesn't work?
Because when Tabs renders TabPane, the default is to render display: none. At this point these components cannot get the correct width or height values. It is recommended to enable lazyRender in or disable keepDOM.
-
Why are the height or width values wrong when using components such as Collapse/Collapsible/Resizable Table in Tabs?
The reason is the same as above. In addition, if the collapse does not need animation, you can also turn off the animation effect by setting its
motionto false. There is no need to get the height of the component at this point。