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.
Examples
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
function callback(key) {
console.log(key);
}
const Demo = () => (
<Tabs defaultActiveKey="1" onChange={callback}>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
);
ReactDOM.render(<Demo />, mountNode);
Tab 1
Tab 2
Tab 3
Tab 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
ReactDOM.render(
<Tabs defaultActiveKey="1">
<TabPane tab="Tab 1" key="1">
Tab 1
</TabPane>
<TabPane tab="Tab 2" disabled key="2">
Tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Tab 3
</TabPane>
</Tabs>,
mountNode,
);
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
const Demo = () => (
<Tabs defaultActiveKey="1" centered>
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
);
ReactDOM.render(<Demo />, mountNode);
Tab 1
Tab 2
Tab 2
import { Tabs } from 'antd';
import { AppleOutlined, AndroidOutlined } from '@ant-design/icons';
const { TabPane } = Tabs;
ReactDOM.render(
<Tabs defaultActiveKey="2">
<TabPane
tab={
<span>
<AppleOutlined />
Tab 1
</span>
}
key="1"
>
Tab 1
</TabPane>
<TabPane
tab={
<span>
<AndroidOutlined />
Tab 2
</span>
}
key="2"
>
Tab 2
</TabPane>
</Tabs>,
mountNode,
);
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
import { Tabs, Radio } from 'antd';
const { TabPane } = Tabs;
class SlidingTabsDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
mode: 'top',
};
}
handleModeChange = e => {
const mode = e.target.value;
this.setState({ mode });
};
render() {
const { mode } = this.state;
return (
<div>
<Radio.Group onChange={this.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 }}>
{[...Array.from({ length: 30 }, (v, i) => i)].map(i => (
<TabPane tab={`Tab-${i}`} key={i} disabled={i === 28}>
Content of tab {i}
</TabPane>
))}
</Tabs>
</div>
);
}
}
ReactDOM.render(<SlidingTabsDemo />, mountNode);
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
import { Tabs, Button, Divider, Checkbox } from 'antd';
const { TabPane } = Tabs;
const CheckboxGroup = Checkbox.Group;
const operations = <Button>Extra Action</Button>;
const OperationsSlot = {
left: <Button className="tabs-extra-demo-button">Left Extra Action</Button>,
right: <Button>Right Extra Action</Button>,
};
const options = ['left', 'right'];
const Demo = () => {
const [position, setPosition] = React.useState(['left', 'right']);
const slot = React.useMemo(() => {
if (position.length === 0) return null;
return position.reduce(
(acc, direction) => ({ ...acc, [direction]: OperationsSlot[direction] }),
{},
);
}, [position]);
return (
<>
<Tabs tabBarExtraContent={operations}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
<br />
<br />
<br />
<div>You can also specify its direction or both side</div>
<Divider />
<CheckboxGroup
options={options}
value={position}
onChange={value => {
setPosition(value);
}}
/>
<br />
<br />
<Tabs tabBarExtraContent={slot}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
</>
);
};
ReactDOM.render(<Demo />, mountNode);
.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
import { Tabs, Radio } from 'antd';
const { TabPane } = Tabs;
class Demo extends React.Component {
state = { size: 'small' };
onChange = e => {
this.setState({ size: e.target.value });
};
render() {
const { size } = this.state;
return (
<div>
<Radio.Group value={size} onChange={this.onChange} style={{ marginBottom: 16 }}>
<Radio.Button value="small">Small</Radio.Button>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="large">Large</Radio.Button>
</Radio.Group>
<Tabs defaultActiveKey="1" size={size} style={{ marginBottom: 32 }}>
<TabPane tab="Tab 1" key="1">
Content of tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of tab 3
</TabPane>
</Tabs>
<Tabs defaultActiveKey="1" type="card" size={size}>
<TabPane tab="Card Tab 1" key="1">
Content of card tab 1
</TabPane>
<TabPane tab="Card Tab 2" key="2">
Content of card tab 2
</TabPane>
<TabPane tab="Card Tab 3" key="3">
Content of card tab 3
</TabPane>
</Tabs>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
Tab position:
Tab 1
Tab 2
Tab 3
Content of Tab 1
import { Tabs, Radio, Space } from 'antd';
const { TabPane } = Tabs;
class Demo extends React.Component {
state = {
tabPosition: 'left',
};
changeTabPosition = e => {
this.setState({ tabPosition: e.target.value });
};
render() {
const { tabPosition } = this.state;
return (
<>
<Space style={{ marginBottom: 24 }}>
Tab position:
<Radio.Group value={tabPosition} onChange={this.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}>
<TabPane tab="Tab 1" key="1">
Content of Tab 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab 3
</TabPane>
</Tabs>
</>
);
}
}
ReactDOM.render(<Demo />, mountNode);
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
function callback(key) {
console.log(key);
}
ReactDOM.render(
<Tabs onChange={callback} type="card">
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>,
mountNode,
);
Tab 1
Tab 2
Tab 3
Content of Tab 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
const initialPanes = [
{ title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
{
title: 'Tab 3',
content: 'Content of Tab 3',
key: '3',
closable: false,
},
];
class Demo extends React.Component {
newTabIndex = 0;
state = {
activeKey: initialPanes[0].key,
panes: initialPanes,
};
onChange = activeKey => {
this.setState({ activeKey });
};
onEdit = (targetKey, action) => {
this[action](targetKey);
};
add = () => {
const { panes } = this.state;
const activeKey = `newTab${this.newTabIndex++}`;
const newPanes = [...panes];
newPanes.push({ title: 'New Tab', content: 'Content of new Tab', key: activeKey });
this.setState({
panes: newPanes,
activeKey,
});
};
remove = targetKey => {
const { panes, activeKey } = this.state;
let newActiveKey = activeKey;
let lastIndex;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter(pane => pane.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
this.setState({
panes: newPanes,
activeKey: newActiveKey,
});
};
render() {
const { panes, activeKey } = this.state;
return (
<Tabs
type="editable-card"
onChange={this.onChange}
activeKey={activeKey}
onEdit={this.onEdit}
>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
{pane.content}
</TabPane>
))}
</Tabs>
);
}
}
ReactDOM.render(<Demo />, mountNode);
Tab Title 1
Tab Title 2
Tab Title 3
Content of Tab Pane 1
Content of Tab Pane 1
Content of Tab Pane 1
import { Tabs } from 'antd';
const { TabPane } = Tabs;
ReactDOM.render(
<div className="card-container">
<Tabs type="card">
<TabPane tab="Tab Title 1" key="1">
<p>Content of Tab Pane 1</p>
<p>Content of Tab Pane 1</p>
<p>Content of Tab Pane 1</p>
</TabPane>
<TabPane tab="Tab Title 2" key="2">
<p>Content of Tab Pane 2</p>
<p>Content of Tab Pane 2</p>
<p>Content of Tab Pane 2</p>
</TabPane>
<TabPane tab="Tab Title 3" key="3">
<p>Content of Tab Pane 3</p>
<p>Content of Tab Pane 3</p>
<p>Content of Tab Pane 3</p>
</TabPane>
</Tabs>
</div>,
mountNode,
);
.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
import { Tabs, Button } from 'antd';
const { TabPane } = Tabs;
class Demo extends React.Component {
constructor(props) {
super(props);
this.newTabIndex = 0;
const panes = new Array(2).fill(null).map((_, index) => {
const id = String(index + 1);
return { title: `Tab ${id}`, content: `Content of Tab Pane ${id}`, key: id };
});
this.state = {
activeKey: panes[0].key,
panes,
};
}
onChange = activeKey => {
this.setState({ activeKey });
};
onEdit = (targetKey, action) => {
this[action](targetKey);
};
add = () => {
const { panes } = this.state;
const activeKey = `newTab${this.newTabIndex++}`;
panes.push({ title: 'New Tab', content: 'New Tab Pane', key: activeKey });
this.setState({ panes, activeKey });
};
remove = targetKey => {
let { activeKey } = this.state;
let lastIndex;
this.state.panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const panes = this.state.panes.filter(pane => pane.key !== targetKey);
if (panes.length && activeKey === targetKey) {
if (lastIndex >= 0) {
activeKey = panes[lastIndex].key;
} else {
activeKey = panes[0].key;
}
}
this.setState({ panes, activeKey });
};
render() {
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button onClick={this.add}>ADD</Button>
</div>
<Tabs
hideAdd
onChange={this.onChange}
activeKey={this.state.activeKey}
type="editable-card"
onEdit={this.onEdit}
>
{this.state.panes.map(pane => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
Content of Tab Pane 1
import { Tabs } from 'antd';
import { StickyContainer, Sticky } from 'react-sticky';
const { TabPane } = Tabs;
const renderTabBar = (props, DefaultTabBar) => (
<Sticky bottomOffset={80}>
{({ style }) => (
<DefaultTabBar {...props} className="site-custom-tab-bar" style={{ ...style }} />
)}
</Sticky>
);
ReactDOM.render(
<StickyContainer>
<Tabs defaultActiveKey="1" renderTabBar={renderTabBar}>
<TabPane tab="Tab 1" key="1" style={{ height: 200 }}>
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
</StickyContainer>,
mountNode,
);
.site-custom-tab-bar {
z-index: 1;
background: #fff;
}
tab 1
tab 2
tab 3
Content of Tab Pane 1
import { Tabs } from 'antd';
import { DndProvider, DragSource, DropTarget } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
const { TabPane } = Tabs;
// Drag & Drop node
class TabNode extends React.Component {
render() {
const { connectDragSource, connectDropTarget, children } = this.props;
return connectDragSource(connectDropTarget(children));
}
}
const cardTarget = {
drop(props, monitor) {
const dragKey = monitor.getItem().index;
const hoverKey = props.index;
if (dragKey === hoverKey) {
return;
}
props.moveTabNode(dragKey, hoverKey);
monitor.getItem().index = hoverKey;
},
};
const cardSource = {
beginDrag(props) {
return {
id: props.id,
index: props.index,
};
},
};
const WrapTabNode = DropTarget('DND_NODE', cardTarget, connect => ({
connectDropTarget: connect.dropTarget(),
}))(
DragSource('DND_NODE', cardSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging(),
}))(TabNode),
);
class DraggableTabs extends React.Component {
state = {
order: [],
};
moveTabNode = (dragKey, hoverKey) => {
const newOrder = this.state.order.slice();
const { children } = this.props;
React.Children.forEach(children, c => {
if (newOrder.indexOf(c.key) === -1) {
newOrder.push(c.key);
}
});
const dragIndex = newOrder.indexOf(dragKey);
const hoverIndex = newOrder.indexOf(hoverKey);
newOrder.splice(dragIndex, 1);
newOrder.splice(hoverIndex, 0, dragKey);
this.setState({
order: newOrder,
});
};
renderTabBar = (props, DefaultTabBar) => (
<DefaultTabBar {...props}>
{node => (
<WrapTabNode key={node.key} index={node.key} moveTabNode={this.moveTabNode}>
{node}
</WrapTabNode>
)}
</DefaultTabBar>
);
render() {
const { order } = this.state;
const { children } = this.props;
const tabs = [];
React.Children.forEach(children, c => {
tabs.push(c);
});
const orderTabs = tabs.slice().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 = tabs.indexOf(a);
const ib = tabs.indexOf(b);
return ia - ib;
});
return (
<DndProvider backend={HTML5Backend}>
<Tabs renderTabBar={this.renderTabBar} {...this.props}>
{orderTabs}
</Tabs>
</DndProvider>
);
}
}
ReactDOM.render(
<DraggableTabs>
<TabPane tab="tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</DraggableTabs>,
mountNode,
);
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 | |
moreIcon | The custom icon of ellipsis | ReactNode | <EllipsisOutlined /> | 4.14.0 |
renderTabBar | Replace the TabBar | (props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement | - | |
size | Preset tab bar size | large | default | small | default | |
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 | object | - | |
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" | (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 |
More option at rc-tabs option
Tabs.TabPane#
Property | Description | Type | Default |
---|---|---|---|
closeIcon | Customize close icon in TabPane's head. Only works while type="editable-card" | ReactNode | - |
forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false |
key | TabPane's key | string | - |
tab | Show text in TabPane's head | ReactNode | - |
More option at rc-tabs option