Tabs
Tabs make it easy to switch between different views.
When To Use#
Ant Design has 3 types of Tabs for different situations.
Card Tabs: for managing too many closeable views.
Normal Tabs: for functional aspects of a page.
Radio.Button: for secondary tabs.
Usage upgrade after 4.23.0#
// works when >=4.23.0, recommended ✅
const items = [
{ label: 'Tab 1', key: 'item-1', children: 'Content 1' }, // remember to pass the key prop
{ label: 'Tab 2', key: 'item-2', children: 'Content 2' },
];
return <Tabs items={items} />;
// works when <4.23.0, deprecated when >=4.23.0 🙅🏻♀️
<Tabs>
<Tabs.TabPane tab="Tab 1" key="item-1">
Content 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="item-2">
Content 2
</Tabs.TabPane>
</Tabs>;
Examples
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
);
export default App;
< 4.23.0
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const onChange = (key: string) => {
console.log(key);
};
const App: React.FC = () => (
<Tabs
defaultActiveKey="1"
onChange={onChange}
items={[
{
label: `Tab 1`,
key: '1',
children: `Content of Tab Pane 1`,
},
{
label: `Tab 2`,
key: '2',
children: `Content of Tab Pane 2`,
},
{
label: `Tab 3`,
key: '3',
children: `Content of Tab Pane 3`,
},
]}
/>
);
export default App;
Tab 1
Tab 2
Tab 3
Tab 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Tabs
defaultActiveKey="1"
items={[
{
label: 'Tab 1',
key: '1',
children: 'Tab 1',
},
{
label: 'Tab 2',
key: '2',
children: 'Tab 2',
disabled: true,
},
{
label: 'Tab 3',
key: '3',
children: 'Tab 3',
},
]}
/>
);
export default App;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Tabs
defaultActiveKey="1"
centered
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;
Tab 1
Tab 2
Tab 2
TypeScript
JavaScript
import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Tabs
defaultActiveKey="2"
items={[AppleOutlined, AndroidOutlined].map((Icon, i) => {
const id = String(i + 1);
return {
label: (
<span>
<Icon />
Tab {id}
</span>
),
key: id,
children: `Tab ${id}`,
};
})}
/>
);
export default App;
Tab-0
Tab-1
Tab-2
Tab-3
Tab-4
Tab-5
Tab-6
Tab-7
Tab-8
Tab-9
Tab-10
Tab-11
Tab-12
Tab-13
Tab-14
Tab-15
Tab-16
Tab-17
Tab-18
Tab-19
Tab-20
Tab-21
Tab-22
Tab-23
Tab-24
Tab-25
Tab-26
Tab-27
Tab-28
Tab-29
Content of tab 1
TypeScript
JavaScript
import type { RadioChangeEvent } from 'antd';
import { Radio, Tabs } from 'antd';
import React, { useState } from 'react';
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
const App: React.FC = () => {
const [mode, setMode] = useState<TabPosition>('top');
const handleModeChange = (e: RadioChangeEvent) => {
setMode(e.target.value);
};
return (
<div>
<Radio.Group onChange={handleModeChange} value={mode} style={{ marginBottom: 8 }}>
<Radio.Button value="top">Horizontal</Radio.Button>
<Radio.Button value="left">Vertical</Radio.Button>
</Radio.Group>
<Tabs
defaultActiveKey="1"
tabPosition={mode}
style={{ height: 220 }}
items={new Array(30).fill(null).map((_, i) => {
const id = String(i);
return {
label: `Tab-${id}`,
key: id,
disabled: i === 28,
children: `Content of tab ${id}`,
};
})}
/>
</div>
);
};
export default App;
Tab 1
Tab 2
Tab 3
Content of tab 1
You can also specify its direction or both side
Tab 1
Tab 2
Tab 3
Content of tab 1
TypeScript
JavaScript
import { Button, Checkbox, Divider, Tabs } from 'antd';
import React, { useMemo, useState } from 'react';
const CheckboxGroup = Checkbox.Group;
const operations = <Button>Extra Action</Button>;
const OperationsSlot: Record<PositionType, React.ReactNode> = {
left: <Button className="tabs-extra-demo-button">Left Extra Action</Button>,
right: <Button>Right Extra Action</Button>,
};
const options = ['left', 'right'];
type PositionType = 'left' | 'right';
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
});
const App: React.FC = () => {
const [position, setPosition] = useState<PositionType[]>(['left', 'right']);
const slot = useMemo(() => {
if (position.length === 0) return null;
return position.reduce(
(acc, direction) => ({ ...acc, [direction]: OperationsSlot[direction] }),
{},
);
}, [position]);
return (
<>
<Tabs tabBarExtraContent={operations} items={items} />
<br />
<br />
<br />
<div>You can also specify its direction or both side</div>
<Divider />
<CheckboxGroup
options={options}
value={position}
onChange={value => {
setPosition(value as PositionType[]);
}}
/>
<br />
<br />
<Tabs tabBarExtraContent={slot} items={items} />
</>
);
};
export default App;
.tabs-extra-demo-button {
margin-right: 16px;
}
.ant-row-rtl .tabs-extra-demo-button {
margin-right: 0;
margin-left: 16px;
}
Tab 1
Tab 2
Tab 3
Content of tab 1
Card Tab 1
Card Tab 2
Card Tab 3
Content of card tab 1
TypeScript
JavaScript
import type { RadioChangeEvent } from 'antd';
import { Radio, Tabs } from 'antd';
import type { SizeType } from 'antd/es/config-provider/SizeContext';
import React, { useState } from 'react';
const App: React.FC = () => {
const [size, setSize] = useState<SizeType>('small');
const onChange = (e: RadioChangeEvent) => {
setSize(e.target.value);
};
return (
<div>
<Radio.Group value={size} onChange={onChange} style={{ marginBottom: 16 }}>
<Radio.Button value="small">Small</Radio.Button>
<Radio.Button value="middle">Middle</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
</Radio.Group>
<Tabs
defaultActiveKey="1"
size={size}
style={{ marginBottom: 32 }}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
defaultActiveKey="1"
type="card"
size={size}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Card Tab ${id}`,
key: id,
children: `Content of card tab ${id}`,
};
})}
/>
</div>
);
};
export default App;
Tab position:
Tab 1
Tab 2
Tab 3
Content of Tab 1
TypeScript
JavaScript
import type { RadioChangeEvent } from 'antd';
import { Radio, Space, Tabs } from 'antd';
import React, { useState } from 'react';
type TabPosition = 'left' | 'right' | 'top' | 'bottom';
const App: React.FC = () => {
const [tabPosition, setTabPosition] = useState<TabPosition>('left');
const changeTabPosition = (e: RadioChangeEvent) => {
setTabPosition(e.target.value);
};
return (
<>
<Space style={{ marginBottom: 24 }}>
Tab position:
<Radio.Group value={tabPosition} onChange={changeTabPosition}>
<Radio.Button value="top">top</Radio.Button>
<Radio.Button value="bottom">bottom</Radio.Button>
<Radio.Button value="left">left</Radio.Button>
<Radio.Button value="right">right</Radio.Button>
</Radio.Group>
</Space>
<Tabs
tabPosition={tabPosition}
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab ${id}`,
};
})}
/>
</>
);
};
export default App;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const onChange = (key: string) => {
console.log(key);
};
const App: React.FC = () => (
<Tabs
onChange={onChange}
type="card"
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;
Tab 1
Tab 2
Tab 3
Content of Tab 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React, { useRef, useState } from 'react';
const initialItems = [
{ label: 'Tab 1', children: 'Content of Tab 1', key: '1' },
{ label: 'Tab 2', children: 'Content of Tab 2', key: '2' },
{
label: 'Tab 3',
children: 'Content of Tab 3',
key: '3',
closable: false,
},
];
const App: React.FC = () => {
const [activeKey, setActiveKey] = useState(initialItems[0].key);
const [items, setItems] = useState(initialItems);
const newTabIndex = useRef(0);
const onChange = (newActiveKey: string) => {
setActiveKey(newActiveKey);
};
const add = () => {
const newActiveKey = `newTab${newTabIndex.current++}`;
const newPanes = [...items];
newPanes.push({ label: 'New Tab', children: 'Content of new Tab', key: newActiveKey });
setItems(newPanes);
setActiveKey(newActiveKey);
};
const remove = (targetKey: string) => {
let newActiveKey = activeKey;
let lastIndex = -1;
items.forEach((item, i) => {
if (item.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = items.filter(item => item.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
setItems(newPanes);
setActiveKey(newActiveKey);
};
const onEdit = (targetKey: string, action: 'add' | 'remove') => {
if (action === 'add') {
add();
} else {
remove(targetKey);
}
};
return (
<Tabs
type="editable-card"
onChange={onChange}
activeKey={activeKey}
onEdit={onEdit}
items={items}
/>
);
};
export default App;
Tab Title 1
Tab Title 2
Tab Title 3
Content of Tab Pane 1
Content of Tab Pane 1
Content of Tab Pane 1
TypeScript
JavaScript
import { Tabs } from 'antd';
import React from 'react';
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab Title ${id}`,
key: id,
children: (
<>
<p>Content of Tab Pane {id}</p>
<p>Content of Tab Pane {id}</p>
<p>Content of Tab Pane {id}</p>
</>
),
};
});
const App: React.FC = () => (
<div className="card-container">
<Tabs type="card" items={items} />
</div>
);
export default App;
.card-container p {
margin: 0;
}
.card-container > .ant-tabs-card .ant-tabs-content {
height: 120px;
margin-top: -16px;
}
.card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
padding: 16px;
background: #fff;
}
.card-container > .ant-tabs-card > .ant-tabs-nav::before {
display: none;
}
.card-container > .ant-tabs-card .ant-tabs-tab,
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-tab {
background: transparent;
border-color: transparent;
}
.card-container > .ant-tabs-card .ant-tabs-tab-active,
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-tab-active {
background: #fff;
border-color: #fff;
}
#components-tabs-demo-card-top .code-box-demo {
padding: 24px;
overflow: hidden;
background: #f5f5f5;
}
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-content {
height: 120px;
margin-top: -8px;
}
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-tab {
background: transparent;
border-color: transparent;
}
[data-theme='dark'] #components-tabs-demo-card-top .code-box-demo {
background: #000;
}
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
background: #141414;
}
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-tab-active {
background: #141414;
border-color: #141414;
}
Tab 1
Tab 2
Content of Tab Pane 1
TypeScript
JavaScript
import { Button, Tabs } from 'antd';
import React, { useRef, useState } from 'react';
const defaultPanes = new Array(2).fill(null).map((_, index) => {
const id = String(index + 1);
return { label: `Tab ${id}`, children: `Content of Tab Pane ${index + 1}`, key: id };
});
const App: React.FC = () => {
const [activeKey, setActiveKey] = useState(defaultPanes[0].key);
const [items, setItems] = useState(defaultPanes);
const newTabIndex = useRef(0);
const onChange = (key: string) => {
setActiveKey(key);
};
const add = () => {
const newActiveKey = `newTab${newTabIndex.current++}`;
setItems([...items, { label: 'New Tab', children: 'New Tab Pane', key: newActiveKey }]);
setActiveKey(newActiveKey);
};
const remove = (targetKey: string) => {
const targetIndex = items.findIndex(pane => pane.key === targetKey);
const newPanes = items.filter(pane => pane.key !== targetKey);
if (newPanes.length && targetKey === activeKey) {
const { key } = newPanes[targetIndex === newPanes.length ? targetIndex - 1 : targetIndex];
setActiveKey(key);
}
setItems(newPanes);
};
const onEdit = (targetKey: string, action: 'add' | 'remove') => {
if (action === 'add') {
add();
} else {
remove(targetKey);
}
};
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button onClick={add}>ADD</Button>
</div>
<Tabs
hideAdd
onChange={onChange}
activeKey={activeKey}
type="editable-card"
onEdit={onEdit}
items={items}
/>
</div>
);
};
export default App;
Content of Tab Pane 1
TypeScript
JavaScript
import type { TabsProps } from 'antd';
import { Tabs } from 'antd';
import React from 'react';
import { Sticky, StickyContainer } from 'react-sticky';
const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
<Sticky bottomOffset={80}>
{({ style }) => (
<DefaultTabBar {...props} className="site-custom-tab-bar" style={{ ...style }} />
)}
</Sticky>
);
const items = new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
style: i === 0 ? { height: 200 } : undefined,
};
});
const App: React.FC = () => (
<StickyContainer>
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar} items={items} />
</StickyContainer>
);
export default App;
.site-custom-tab-bar {
z-index: 1;
background: #fff;
}
tab 1
tab 2
tab 3
Content of Tab Pane 1
TypeScript
JavaScript
import type { TabsProps } from 'antd';
import { Tabs } from 'antd';
import React, { useRef, useState } from 'react';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
const type = 'DraggableTabNode';
interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
index: React.Key;
moveNode: (dragIndex: React.Key, hoverIndex: React.Key) => void;
}
const DraggableTabNode = ({ index, children, moveNode }: DraggableTabPaneProps) => {
const ref = useRef<HTMLDivElement>(null);
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: monitor => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: 'dropping',
};
},
drop: (item: { index: React.Key }) => {
moveNode(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<div ref={ref} style={{ marginRight: 24 }} className={isOver ? dropClassName : ''}>
{children}
</div>
);
};
const DraggableTabs: React.FC<TabsProps> = props => {
const { items = [] } = props;
const [order, setOrder] = useState<React.Key[]>([]);
const moveTabNode = (dragKey: React.Key, hoverKey: React.Key) => {
const newOrder = order.slice();
items.forEach(item => {
if (item.key && newOrder.indexOf(item.key) === -1) {
newOrder.push(item.key);
}
});
const dragIndex = newOrder.indexOf(dragKey);
const hoverIndex = newOrder.indexOf(hoverKey);
newOrder.splice(dragIndex, 1);
newOrder.splice(hoverIndex, 0, dragKey);
setOrder(newOrder);
};
const renderTabBar: TabsProps['renderTabBar'] = (tabBarProps, DefaultTabBar) => (
<DefaultTabBar {...tabBarProps}>
{node => (
<DraggableTabNode key={node.key} index={node.key!} moveNode={moveTabNode}>
{node}
</DraggableTabNode>
)}
</DefaultTabBar>
);
const orderItems = [...items].sort((a, b) => {
const orderA = order.indexOf(a.key!);
const orderB = order.indexOf(b.key!);
if (orderA !== -1 && orderB !== -1) {
return orderA - orderB;
}
if (orderA !== -1) {
return -1;
}
if (orderB !== -1) {
return 1;
}
const ia = items.indexOf(a);
const ib = items.indexOf(b);
return ia - ib;
});
return (
<DndProvider backend={HTML5Backend}>
<Tabs renderTabBar={renderTabBar} {...props} items={orderItems} />
</DndProvider>
);
};
const App: React.FC = () => (
<DraggableTabs
items={new Array(3).fill(null).map((_, i) => {
const id = String(i + 1);
return {
label: `tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
);
export default App;
.dropping {
background: #fefefe;
transition: all 0.3s;
}
API#
Tabs#
Property | Description | Type | Default | Version | |
---|---|---|---|---|---|
activeKey | Current TabPane's key | string | - | ||
addIcon | Customize add icon | ReactNode | - | 4.4.0 | |
animated | Whether to change tabs with animation. Only works while tabPosition="top" | boolean | { inkBar: boolean, tabPane: boolean } | { inkBar: true, tabPane: false } | ||
centered | Centers tabs | boolean | false | 4.4.0 | |
defaultActiveKey | Initial active TabPane's key, if activeKey is not set | string | - | ||
hideAdd | Hide plus icon or not. Only works while type="editable-card" | boolean | false | ||
items | Configure tab content | TabItem | [] | 4.23.0 | |
moreIcon | The custom icon of ellipsis | ReactNode | <EllipsisOutlined /> | 4.14.0 | |
popupClassName | className for more dropdown. | string | - | 4.21.0 | |
renderTabBar | Replace the TabBar | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | ||
size | Preset tab bar size | large | middle | small | middle | ||
tabBarExtraContent | Extra content in tab bar | ReactNode | {left?: ReactNode, right?: ReactNode} | - | object: 4.6.0 | |
tabBarGutter | The gap between tabs | number | - | ||
tabBarStyle | Tab bar style object | CSSProperties | - | ||
tabPosition | Position of tabs | top | right | bottom \ | left | top | |
destroyInactiveTabPane | Whether destroy inactive TabPane when change tab | boolean | false | ||
type | Basic style of tabs | line | card | editable-card | line | ||
onChange | Callback executed when active tab is changed | function(activeKey) {} | - | ||
onEdit | Callback executed when tab is added or removed. Only works while type="editable-card" | (action === 'add' ? event : targetKey, action): void | - | ||
onTabClick | Callback executed when tab is clicked | function(key: string, event: MouseEvent) | - | ||
onTabScroll | Trigger when tab scroll | function({ direction: left | right | top | bottom }) | - | 4.3.0 | |
items | TabItem content | TabItemType | [] | 4.23.0 |
More option at rc-tabs tabs
TabItemType#
Property | Description | Type | Default |
---|---|---|---|
closeIcon | Customize close icon in TabPane's head. Only works while type="editable-card" | ReactNode | - |
disabled | Set TabPane disabled | boolean | false |
forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false |
key | TabPane's key | string | - |
label | TabPane's head display text | ReactNode | - |
children | TabPane's head display content | ReactNode | - |