logoAnt Design

⌘ K
  • 设计
  • 研发
  • 组件
  • 博客
  • 资源
5.3.3
  • 组件总览
  • 通用
    • Button按钮
    • Icon图标
    • Typography排版
  • 布局
    • Divider分割线
    • Grid栅格
    • Layout布局
    • Space间距
  • 导航
    • Anchor锚点
    • Breadcrumb面包屑
    • Dropdown下拉菜单
    • Menu导航菜单
    • Pagination分页
    • Steps步骤条
  • 数据录入
    • AutoComplete自动完成
    • Cascader级联选择
    • Checkbox多选框
    • DatePicker日期选择框
    • Form表单
    • Input输入框
    • InputNumber数字输入框
    • Mentions提及
    • Radio单选框
    • Rate评分
    • Select选择器
    • Slider滑动输入条
    • Switch开关
    • TimePicker时间选择框
    • Transfer穿梭框
    • TreeSelect树选择
    • Upload上传
  • 数据展示
    • Avatar头像
    • Badge徽标数
    • Calendar日历
    • Card卡片
    • Carousel走马灯
    • Collapse折叠面板
    • Descriptions描述列表
    • Empty空状态
    • Image图片
    • List列表
    • Popover气泡卡片
    • QRCode二维码
    • Segmented分段控制器
    • Statistic统计数值
    • Table表格
    • Tabs标签页
    • Tag标签
    • Timeline时间轴
    • Tooltip文字提示
    • Tour漫游式引导
    • Tree树形控件
  • 反馈
    • Alert警告提示
    • Drawer抽屉
    • Message全局提示
    • Modal对话框
    • Notification通知提醒框
    • Popconfirm气泡确认框
    • Progress进度条
    • Result结果
    • Skeleton骨架屏
    • Spin加载中
  • 其他
    • Affix固钉
    • App包裹组件
    • ConfigProvider全局化配置
    • FloatButton悬浮按钮
    • Watermark水印
何时使用
代码演示
基本
禁用
居中
图标
滑动
附加内容
大小
位置
卡片式页签
新增和关闭页签
自定义新增页签触发器
自定义页签头
可拖拽标签
API
Tabs
TabItemType

Tabs标签页

Table表格Tag标签

相关资源

Ant Design Charts
Ant Design Pro
Ant Design Pro Components
Ant Design Mobile
Ant Design Mini
Ant Design Landing-首页模板集
Scaffolds-脚手架市场
Umi-React 应用开发框架
dumi-组件/文档研发工具
qiankun-微前端框架
ahooks-React Hooks 库
Ant Motion-设计动效
国内镜像站点 🇨🇳

社区

Awesome Ant Design
Medium
Twitter
yuqueAnt Design 语雀专栏
Ant Design 知乎专栏
体验科技专栏
seeconfSEE Conf-蚂蚁体验科技大会
加入我们

帮助

GitHub
更新日志
常见问题
报告 Bug
议题
讨论区
StackOverflow
SegmentFault

Ant XTech更多产品

yuque语雀-构建你的数字花园
AntVAntV-数据可视化解决方案
EggEgg-企业级 Node.js 框架
kitchenKitchen-Sketch 工具集
xtech蚂蚁体验科技
主题编辑器
Made with ❤ by
蚂蚁集团和 Ant Design 开源社区

选项卡切换组件。

何时使用

提供平级的区域将大块内容进行收纳和展现,保持界面整洁。

Ant Design 依次提供了三级选项卡,分别用于不同的场景。

  • 卡片式的页签,提供可关闭的样式,常用于容器顶部。
  • 既可用于容器顶部,也可用于容器内部,是最通用的 Tabs。
  • Radio.Button 可作为更次级的页签来使用。

代码演示

Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
基本

默认选中第一项。

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;
Tab 1
Tab 2
Tab 3
Tab 1
禁用

禁用某一项。

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;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
居中

标签居中展示。

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;
Tab 1
Tab 2
Tab 2
图标

有图标的标签。

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;
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
滑动

可以左右、上下滑动,容纳更多标签。

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;
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
附加内容

可以在页签两边添加附加操作。

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;
}
Tab 1
Tab 2
Tab 3
Content of tab 1
Card Tab 1
Card Tab 2
Card Tab 3
Content of card tab 1
大小

大号页签用在页头区域,小号用在弹出框等较狭窄的容器内。

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;
Tab position:
Tab 1
Tab 2
Tab 3
Content of Tab 1
位置

有四个位置,tabPosition="left|right|top|bottom"。在移动端下,left|right 会自动切换成 top。

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;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
卡片式页签

另一种样式的页签,不提供对应的垂直样式。

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;
Tab 1
Tab 2
Tab 3
Content of Tab 1
新增和关闭页签

只有卡片样式的页签支持新增和关闭选项。使用 closable={false} 禁止关闭。

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;
Tab 1
Tab 2
Content of Tab Pane 1
自定义新增页签触发器

隐藏默认的页签增加图标,给自定义触发器绑定事件。

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;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
自定义页签头

使用 react-sticky-box 和 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;
Tab 1
Tab 2
Tab 3
Content of Tab Pane 1
可拖拽标签

使用 dnd-kit 实现标签可拖拽。

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

参数说明类型默认值版本
activeKey当前激活 tab 面板的 keystring-
addIcon自定义添加按钮ReactNode-4.4.0
animated是否使用动画切换 Tabs, 仅生效于 tabPosition="top"boolean| { inkBar: boolean, tabPane: boolean }{ inkBar: true, tabPane: false }
centered标签居中展示booleanfalse4.4.0
defaultActiveKey初始化选中面板的 key,如果没有设置 activeKeystring第一个面板
hideAdd是否隐藏加号图标,在 type="editable-card" 时有效booleanfalse
items配置选项卡内容TabItemType[]4.23.0
moreIcon自定义折叠 iconReactNode<EllipsisOutlined />4.14.0
popupClassName更多菜单的 classNamestring-4.21.0
renderTabBar替换 TabBar,用于二次封装标签头(props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement-
size大小,提供 large middle 和 small 三种大小stringmiddle
tabBarExtraContenttab bar 上额外的元素ReactNode | {left?: ReactNode, right?: ReactNode}-object: 4.6.0
tabBarGuttertabs 之间的间隙number-
tabBarStyletab bar 的样式对象CSSProperties-
tabPosition页签位置,可选值有 top right bottom leftstringtop
destroyInactiveTabPane被隐藏时是否销毁 DOM 结构booleanfalse
type页签的基本样式,可选 line、card editable-card 类型stringline
onChange切换面板的回调function(activeKey) {}-
onEdit新增和删除页签的回调,在 type="editable-card" 时有效(action === 'add' ? event : targetKey, action): void-
onTabClicktab 被点击的回调function(key: string, event: MouseEvent)-
onTabScrolltab 滚动时触发function({ direction: left | right | top | bottom })-4.3.0

更多属性查看 rc-tabs tabs

TabItemType

参数说明类型默认值
closeIcon自定义关闭图标,在 type="editable-card"时有效ReactNode-
disabled禁用某一项booleanfalse
forceRender被隐藏时是否渲染 DOM 结构booleanfalse
key对应 activeKeystring-
label选项卡头显示文字ReactNode-
children选项卡头显示内容ReactNode-