logoAnt Design

⌘ K
  • Design
  • Development
  • Components
  • Blog
  • Resources
5.3.2
  • Components Overview
  • General
    • Button
    • Icon
    • Typography
  • Layout
    • Divider
    • Grid
    • Layout
    • Space
  • Navigation
    • Anchor
    • Breadcrumb
    • Dropdown
    • Menu
    • Pagination
    • Steps
  • Data Entry
    • AutoComplete
    • Cascader
    • Checkbox
    • DatePicker
    • Form
    • Input
    • InputNumber
    • Mentions
    • Radio
    • Rate
    • Select
    • Slider
    • Switch
    • TimePicker
    • Transfer
    • TreeSelect
    • Upload
  • Data Display
    • Avatar
    • Badge
    • Calendar
    • Card
    • Carousel
    • Collapse
    • Descriptions
    • Empty
    • Image
    • List
    • Popover
    • QRCode
    • Segmented
    • Statistic
    • Table
    • Tabs
    • Tag
    • Timeline
    • Tooltip
    • Tour
    • Tree
  • Feedback
    • Alert
    • Drawer
    • Message
    • Modal
    • Notification
    • Popconfirm
    • Progress
    • Result
    • Skeleton
    • Spin
  • Other
    • Affix
    • App
    • ConfigProvider
    • FloatButton
    • Watermark
Examples
Basic
Disabled
Centered
Icon
Slide
Extra content
Size
Position
Card type tab
Add & close tab
Customized trigger of new tab
Customized bar of tab
Draggable Tabs
API
Tabs
TabItemType

Tabs

TableTag

Resources

Ant Design Charts
Ant Design Pro
Ant Design Pro Components
Ant Design Mobile
Ant Design Mini
Ant Design Landing-Landing Templates
Scaffolds-Scaffold Market
Umi-React Application Framework
dumi-Component doc generator
qiankun-Micro-Frontends Framework
ahooks-React Hooks Library
Ant Motion-Motion Solution
China Mirror 🇨🇳

Community

Awesome Ant Design
Medium
Twitter
yuqueAnt Design in YuQue
Ant Design in Zhihu
Experience Cloud Blog
seeconfSEE Conf-Experience Tech Conference

Help

GitHub
Change Log
FAQ
Bug Report
Issues
Discussions
StackOverflow
SegmentFault

Ant XTechMore Products

yuqueYuQue-Document Collaboration Platform
AntVAntV-Data Visualization
EggEgg-Enterprise Node.js Framework
kitchenKitchen-Sketch Toolkit
xtechAnt Financial Experience Tech
Theme Editor
Made with ❤ by
Ant Group and Ant Design Community

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

Basic

Default activate first tab.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import { Tabs } from 'antd';
import type { TabsProps } from 'antd';

const onChange = (key: string) => {
  console.log(key);
};

const items: TabsProps['items'] = [
  {
    key: '1',
    label: `Tab 1`,
    children: `Content of Tab Pane 1`,
  },
  {
    key: '2',
    label: `Tab 2`,
    children: `Content of Tab Pane 2`,
  },
  {
    key: '3',
    label: `Tab 3`,
    children: `Content of Tab Pane 3`,
  },
];

const App: React.FC = () => <Tabs defaultActiveKey="1" items={items} onChange={onChange} />;

export default App;
Disabled

Disabled a tab.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import { Tabs } from 'antd';

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;
Centered

Centered tabs.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import { Tabs } from 'antd';

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;
Icon

The Tab with Icon.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import { AndroidOutlined, AppleOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';

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;
Slide

In order to fit in more tabs, they can slide left and right (or up and down).

expand codeexpand code
TypeScript
JavaScript
import React, { useState } from 'react';
import type { RadioChangeEvent } from 'antd';
import { Radio, Tabs } from 'antd';

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;
Extra content

You can add extra actions to the right or left or even both side of Tabs.

expand codeexpand code
TypeScript
JavaScript
import React, { useMemo, useState } from 'react';
import { Button, Checkbox, Divider, Tabs } from 'antd';

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;
}
Size

Large size tabs are usually used in page header, and small size could be used in Modal.

expand codeexpand code
TypeScript
JavaScript
import React, { useState } from 'react';
import type { RadioChangeEvent } from 'antd';
import { Radio, Tabs } from 'antd';
import type { SizeType } from 'antd/es/config-provider/SizeContext';

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;
Position

Tab's position: left, right, top or bottom. Will auto switch to top in mobile.

expand codeexpand code
TypeScript
JavaScript
import React, { useState } from 'react';
import type { RadioChangeEvent } from 'antd';
import { Radio, Space, Tabs } from 'antd';

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;
Card type tab

Another type of Tabs, which doesn't support vertical mode.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import { Tabs } from 'antd';

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;
Add & close tab

Only card type Tabs support adding & closable. +Use closable={false} to disable close.

expand codeexpand code
TypeScript
JavaScript
import React, { useRef, useState } from 'react';
import { Tabs } from 'antd';

type TargetKey = React.MouseEvent | React.KeyboardEvent | string;

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: TargetKey) => {
    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: React.MouseEvent | React.KeyboardEvent | 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;
Customized trigger of new tab

Hide default plus icon, and bind event for customized trigger.

expand codeexpand code
TypeScript
JavaScript
import React, { useRef, useState } from 'react';
import { Button, Tabs } from 'antd';

type TargetKey = React.MouseEvent | React.KeyboardEvent | string;

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: TargetKey) => {
    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: TargetKey, 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;
Customized bar of tab

Use react-sticky-box and renderTabBar.

expand codeexpand code
TypeScript
JavaScript
import React from 'react';
import type { TabsProps } from 'antd';
import { Tabs, theme } from 'antd';
import StickyBox from 'react-sticky-box';

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 = () => {
  const {
    token: { colorBgContainer },
  } = theme.useToken();
  const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
    <StickyBox offsetTop={0} offsetBottom={20} style={{ zIndex: 1 }}>
      <DefaultTabBar {...props} style={{ background: colorBgContainer }} />
    </StickyBox>
  );
  return <Tabs defaultActiveKey="1" renderTabBar={renderTabBar} items={items} />;
};

export default App;
Draggable Tabs

Use dnd-kit to make tabs draggable.

expand codeexpand code
TypeScript
JavaScript
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
import {
  arrayMove,
  horizontalListSortingStrategy,
  SortableContext,
  useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { css } from '@emotion/css';
import { Tabs } from 'antd';
import React, { useEffect, useState } from 'react';

interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
  'data-node-key': string;
  onActiveBarTransform: (className: string) => void;
}

const DraggableTabNode = ({ className, onActiveBarTransform, ...props }: DraggableTabPaneProps) => {
  const { attributes, listeners, setNodeRef, transform, transition, isSorting } = useSortable({
    id: props['data-node-key'],
  });

  const style: React.CSSProperties = {
    ...props.style,
    transform: CSS.Transform.toString(transform),
    transition,
    cursor: 'move',
  };

  useEffect(() => {
    if (!isSorting) {
      onActiveBarTransform('');
    } else if (className?.includes('ant-tabs-tab-active')) {
      onActiveBarTransform(
        css`
          .ant-tabs-ink-bar {
            transform: ${CSS.Transform.toString(transform)};
            transition: ${transition} !important;
          }
        `,
      );
    }
  }, [className, isSorting, transform]);

  return React.cloneElement(props.children as React.ReactElement, {
    ref: setNodeRef,
    style,
    ...attributes,
    ...listeners,
  });
};

const App: React.FC = () => {
  const [items, setItems] = useState([
    {
      key: '1',
      label: `Tab 1`,
      children: 'Content of Tab Pane 1',
    },
    {
      key: '2',
      label: `Tab 2`,
      children: 'Content of Tab Pane 2',
    },
    {
      key: '3',
      label: `Tab 3`,
      children: 'Content of Tab Pane 3',
    },
  ]);

  const [className, setClassName] = useState('');

  const sensor = useSensor(PointerSensor, { activationConstraint: { distance: 10 } });

  const onDragEnd = ({ active, over }: DragEndEvent) => {
    if (active.id !== over?.id) {
      setItems((prev) => {
        const activeIndex = prev.findIndex((i) => i.key === active.id);
        const overIndex = prev.findIndex((i) => i.key === over?.id);
        return arrayMove(prev, activeIndex, overIndex);
      });
    }
  };

  return (
    <Tabs
      className={className}
      items={items}
      renderTabBar={(tabBarProps, DefaultTabBar) => (
        <DndContext sensors={[sensor]} onDragEnd={onDragEnd}>
          <SortableContext items={items.map((i) => i.key)} strategy={horizontalListSortingStrategy}>
            <DefaultTabBar {...tabBarProps}>
              {(node) => (
                <DraggableTabNode
                  {...node.props}
                  key={node.key}
                  onActiveBarTransform={setClassName}
                >
                  {node}
                </DraggableTabNode>
              )}
            </DefaultTabBar>
          </SortableContext>
        </DndContext>
      )}
    />
  );
};

export default App;

API

Tabs

PropertyDescriptionTypeDefaultVersion
activeKeyCurrent TabPane's keystring-
addIconCustomize add iconReactNode-4.4.0
animatedWhether to change tabs with animation. Only works while tabPosition="top"boolean | { inkBar: boolean, tabPane: boolean }{ inkBar: true, tabPane: false }
centeredCenters tabsbooleanfalse4.4.0
defaultActiveKeyInitial active TabPane's key, if activeKey is not setstring-
hideAddHide plus icon or not. Only works while type="editable-card"booleanfalse
itemsConfigure tab contentTabItemType[]4.23.0
moreIconThe custom icon of ellipsisReactNode<EllipsisOutlined />4.14.0
popupClassNameclassName for more dropdown.string-4.21.0
renderTabBarReplace the TabBar(props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement-
sizePreset tab bar sizelarge | middle | smallmiddle
tabBarExtraContentExtra content in tab barReactNode | {left?: ReactNode, right?: ReactNode}-object: 4.6.0
tabBarGutterThe gap between tabsnumber-
tabBarStyleTab bar style objectCSSProperties-
tabPositionPosition of tabstop | right | bottom | lefttop
destroyInactiveTabPaneWhether destroy inactive TabPane when change tabbooleanfalse
typeBasic style of tabsline | card | editable-cardline
onChangeCallback executed when active tab is changedfunction(activeKey) {}-
onEditCallback executed when tab is added or removed. Only works while type="editable-card"(action === 'add' ? event : targetKey, action): void-
onTabClickCallback executed when tab is clickedfunction(key: string, event: MouseEvent)-
onTabScrollTrigger when tab scrollfunction({ direction: left | right | top | bottom })-4.3.0

More option at rc-tabs tabs

TabItemType

PropertyDescriptionTypeDefault
closeIconCustomize close icon in TabPane's head. Only works while type="editable-card"ReactNode-
disabledSet TabPane disabledbooleanfalse
forceRenderForced render of content in tabs, not lazy render after clicking on tabsbooleanfalse
keyTabPane's keystring-
labelTabPane's head display textReactNode-
childrenTabPane's head display contentReactNode-
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab 1
Tab 2
Tab 3
Tab 1
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab 1
Tab 2
Tab 2
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
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
Tab 1
Tab 2
Tab 3
Content of tab 1
Card Tab 1
Card Tab 2
Card Tab 3
Content of card tab 1
Tab position:
Tab 1
Tab 2
Tab 3
Content of Tab 1
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab 1
Tab 2
Tab 3
Content of Tab 1
Tab 1
Tab 2
Content of Tab Pane 1
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1