Tabs 标签页

Tabs 标签页

当内容需要分组并在不同模块页面中展示,可使用 Tabs 标签栏对不同的组/页之间进行切换。

底层基于 reka-ui 无头组件,内置 ARIA 无障碍支持和键盘导航。

何时使用

  • 需要在不同模块页面之间切换展示内容时。
  • 内容需要分组展示时。
  • 节省页面空间,将相关内容组织在一起时。

导入

import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@reglow/reui";
import type { TabItem } from "@reglow/reui";

两种使用方式

方式一:配置驱动(tabList)

适合标签数据来自接口或动态变化的场景:

<script setup>
import { ref } from "vue";
import { Tabs } from "@reglow/reui";

const activeKey = ref("1");
const tabList = [
  { itemKey: "1", tab: "文档" },
  { itemKey: "2", tab: "快速起步" },
  { itemKey: "3", tab: "帮助" },
];
</script>

<template>
  <Tabs v-model:active-key="activeKey" :tab-list="tabList" type="line">
    <div v-if="activeKey === '1'">文档内容</div>
    <div v-if="activeKey === '2'">快速起步内容</div>
    <div v-if="activeKey === '3'">帮助内容</div>
  </Tabs>
</template>

方式二:复合组件(手动组装)

适合自定义结构、内容就近放置的场景:

<script setup>
import { ref } from "vue";
import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@reglow/reui";

const activeKey = ref("1");
</script>

<template>
  <Tabs v-model:active-key="activeKey" type="line">
    <TabsList>
      <TabsTrigger value="1">文档</TabsTrigger>
      <TabsTrigger value="2">帮助</TabsTrigger>
    </TabsList>
    <TabsContent value="1">文档内容</TabsContent>
    <TabsContent value="2">帮助内容</TabsContent>
  </Tabs>
</template>

type 属性从 Tabs 通过 provide/inject 自动传递给 TabsList 和 TabsTrigger,无需在子组件上重复指定。

代码演示

六种样式

<!-- 线条式(默认) -->
<Tabs :tab-list="tabList" type="line">...</Tabs>

<!-- 按钮式 -->
<Tabs :tab-list="tabList" type="button">...</Tabs>

<!-- 卡片式 -->
<Tabs :tab-list="tabList" type="card">...</Tabs>

<!-- 斜线式 -->
<Tabs :tab-list="tabList" type="slash">...</Tabs>

<!-- 可编辑卡片式 -->
<Tabs
  :tab-list="tabList"
  type="editable-card"
  @add="onAdd"
  @close="onClose"
>...</Tabs>

<!-- 分段控制器式 -->
<Tabs :tab-list="tabList" type="segmented">...</Tabs>

分段控制器式(segmented)

segmented 类型的标签栏样式与 Segmented 组件一致,选中项有滑块背景动画。

<script setup>
import { ref } from "vue";
import { Tabs } from "@reglow/reui";

const activeKey = ref("1");
const tabList = [
  { itemKey: "1", tab: "日" },
  { itemKey: "2", tab: "周" },
  { itemKey: "3", tab: "月" },
  { itemKey: "4", tab: "年" },
];
</script>

<template>
  <Tabs v-model:active-key="activeKey" :tab-list="tabList" type="segmented">
    <div v-if="activeKey === '1'">按日查看</div>
    <div v-if="activeKey === '2'">按周查看</div>
    <div v-if="activeKey === '3'">按月查看</div>
    <div v-if="activeKey === '4'">按年查看</div>
  </Tabs>
</template>

可编辑卡片式(editable-card)

editable-card 类型默认显示关闭按钮和新增按钮,适合动态标签页场景。

<script setup>
import { ref } from "vue";
import { Tabs } from "@reglow/reui";

const activeKey = ref("1");
const tabList = ref([
  { itemKey: "1", tab: "标签1" },
  { itemKey: "2", tab: "标签2" },
]);

let counter = 3;

function onAdd() {
  const key = String(counter++);
  tabList.value.push({ itemKey: key, tab: `标签${key}` });
  activeKey.value = key;
}

function onClose(key) {
  const idx = tabList.value.findIndex((t) => t.itemKey === key);
  tabList.value = tabList.value.filter((t) => t.itemKey !== key);
  if (activeKey.value === key && tabList.value.length > 0) {
    activeKey.value = tabList.value[Math.max(0, idx - 1)].itemKey;
  }
}
</script>

<template>
  <Tabs
    v-model:active-key="activeKey"
    :tab-list="tabList"
    type="editable-card"
    @add="onAdd"
    @close="onClose"
  >
    <div
      v-for="item in tabList"
      :key="item.itemKey"
      v-show="activeKey === item.itemKey"
    >
      {{ item.tab }}内容
    </div>
  </Tabs>
</template>

可以通过 addable 和 editable 属性控制新增和关闭按钮的显示:

<!-- 禁用新增按钮 -->
<Tabs :tab-list="tabList" type="editable-card" :addable="false">...</Tabs>

<!-- 禁用关闭按钮 -->
<Tabs :tab-list="tabList" type="editable-card" :editable="false">...</Tabs>

复合组件 + editable-card

复合组件模式下,TabsList 自动渲染新增按钮,TabsTrigger 自动渲染关闭按钮:

<Tabs
  v-model:active-key="active"
  type="editable-card"
  @add="onAdd"
  @close="onClose"
>
  <TabsList>
    <TabsTrigger v-for="t in tabs" :key="t.key" :value="t.key">
      {{ t.label }}
    </TabsTrigger>
  </TabsList>
  <TabsContent v-for="t in tabs" :key="t.key" :value="t.key">
    {{ t.label }}内容
  </TabsContent>
</Tabs>

带图标

通过 icon 属性添加图标(配置式):

<script setup>
import { h } from "vue";
import { File, Globe, HelpCircle } from "lucide-vue-next";

const tabList = [
  { itemKey: "1", tab: "文档", icon: h(File) },
  { itemKey: "2", tab: "快速起步", icon: h(Globe) },
  { itemKey: "3", tab: "帮助", icon: h(HelpCircle) },
];
</script>

<template>
  <Tabs :tab-list="tabList" />
</template>

复合组件模式下直接在 TabsTrigger 内放置图标:

<TabsList>
  <TabsTrigger value="1">
    <File class="size-4" /> 文档
  </TabsTrigger>
</TabsList>

垂直标签栏

设置 tabPosition 为 left 或 right。

<template>
  <Tabs :tab-list="tabList" type="line" tab-position="left">
    <div>内容</div>
  </Tabs>
</template>

禁用

<script setup>
const tabList = [
  { itemKey: "1", tab: "文档" },
  { itemKey: "2", tab: "快速起步", disabled: true },
  { itemKey: "3", tab: "帮助" },
];
</script>

<template>
  <Tabs :tab-list="tabList" default-active-key="1" />
</template>

标签栏额外内容

通过 tabBarExtraContent 在标签栏添加附加操作(仅配置式模式)。

<script setup>
import { h } from "vue";
import { Button } from "@reglow/reui";

const extra = h(Button, { size: "sm" }, () => "额外操作");
</script>

<template>
  <Tabs :tab-list="tabList" :tab-bar-extra-content="extra">
    <div>内容</div>
  </Tabs>
</template>

尺寸

<template>
  <Tabs :tab-list="tabList" size="small">小</Tabs>
  <Tabs :tab-list="tabList" size="default">默认</Tabs>
  <Tabs :tab-list="tabList" size="large">大</Tabs>
</template>

可关闭

设置 closable 为 true(配置式)。

<script setup>
const tabList = [
  { itemKey: "1", tab: "标签1", closable: true },
  { itemKey: "2", tab: "标签2", closable: true },
];
</script>

<template>
  <Tabs :tab-list="tabList" @close="(key) => console.log('关闭', key)" />
</template>

复合组件模式下,在 TabsTrigger 上设置 closable:

<TabsList>
  <TabsTrigger value="1" closable>标签1</TabsTrigger>
</TabsList>

受控

<script setup>
import { ref } from "vue";
const activeKey = ref("1");

const onChange = (key) => {
  console.log("切换到:", key);
};
</script>

<template>
  <Tabs v-model:active-key="activeKey" :tab-list="tabList" @change="onChange" />
</template>

keepDOM

设置 keepDOM 为 false 时只渲染当前面板(仅复合组件模式生效)。

<template>
  <Tabs v-model:active-key="active" :keep-d-o-m="false" type="segmented">
    <TabsList>
      <TabsTrigger value="1">标签1</TabsTrigger>
      <TabsTrigger value="2">标签2</TabsTrigger>
    </TabsList>
    <TabsContent value="1">内容1</TabsContent>
    <TabsContent value="2">内容2</TabsContent>
  </Tabs>
</template>

API

Tabs Props

参数说明类型默认值
activeKey (v-model)当前激活的 keystring-
defaultActiveKey默认激活的 keystring第一项
type标签栏样式line | button | card | slash | editable-card | segmentedline
tabPosition标签栏位置top | left | right | bottomtop
tabList标签页列表(配置式)TabItem[][]
keepDOM是否保持 DOM(复合组件模式)booleanfalse
size尺寸small | default | largedefault
addable是否可新增标签(仅 editable-card)booleantrue
editable是否可关闭标签(仅 editable-card)booleantrue
tabBarExtraContent标签栏额外内容(仅配置式)VNode | string-
class附加类名string-
style附加样式CSSProperties-

Tabs Events

事件说明回调参数
change激活项变化(key: string)
tabClick点击标签(key: string, e: MouseEvent)
close关闭标签(key: string)
add新增标签(editable-card)()

TabsList Props

参数说明类型默认值
class附加类名string-

TabsTrigger Props

参数说明类型默认值
value标签页标识(必填)string-
disabled是否禁用booleanfalse
closable是否可关闭booleanfalse
class附加类名string-

TabsContent Props

参数说明类型默认值
value标签页标识(必填)string-
class附加类名string-

TabItem

参数说明类型默认值
itemKey标签页标识(必填)string-
tab标签页标题string | VNode-
disabled是否禁用booleanfalse
icon图标VNode-
closable是否可关闭booleanfalse

样式对照

type说明
line线条式,选中项有底部/侧边高亮线
button按钮式,选中项有背景色填充
card卡片式,选中项有边框连接
slash斜线式,标签倾斜显示
editable-card可编辑卡片式,默认可关闭和新增标签
segmented分段控制器式,选中项有滑块背景动画

无障碍

底层基于 reka-ui,内置以下无障碍特性:

  • role="tablist" / role="tab" / role="tabpanel" ARIA 角色
  • 键盘导航:←→↑↓ 切换标签,Home / End 跳转首尾
  • aria-selected / aria-controls / aria-labelledby 属性自动管理
  • 焦点管理:选中标签自动获得焦点