标签页
概述
一组分层的内容区块——即标签面板——一次只显示一个。
特性
- WAI-ARIA 标签页模式 — 列表渲染为
tablist,触发器为role="tab",面板为role="tabpanel";活动标签带aria-selected,每个触发器通过aria-controls/aria-labelledby关联面板,aria-orientation反映布局方向。 - 数据驱动的 Compact 组合 —
STabs将条目迭代、默认触发器/内容组合与指示器渲染委托给泛型 AriaTabsCompact<T>,由它负责每个条目的 ARIA 接线。 - 受控/非受控状态 —
modelValue支持v-model(受控);defaultValue提供非受控用法,底层基于useControllableState。 - 两种激活模式 —
activationMode: 'automatic'在聚焦时激活标签(ARIA 默认);'manual'仅在点击 /Enter/Space时激活。 - 完整键盘导航 —
useRovingFocusGroup提供方向键(与 Home/End)移动并跳过禁用标签;Enter/Space激活聚焦的标签。 - 基于 Presence 的内容挂载 —
unmountOnHide: true(默认)在退出动画后卸载非活动面板;false保持所有面板挂载但带hidden属性;forceMount无条件将面板保留在 DOM 中。 - 动画指示器 — 滑动指示器通过 CSS 变量(
--vean-tabs-indicator-size/--vean-tabs-indicator-position)追踪活动标签,由ResizeObserver测量并在值/方向变化时重新定位;RTL 偏移自动镜像。 - 水平与垂直布局 —
orientation: 'vertical'纵向堆叠列表并让指示器沿块轴移动。 - 三个自定义插槽 —
trigger(scoped{ ...item, active })、content(scoped{ ...item, active })与indicator。 - 六种视觉变体 —
size(xs…2xl)、orientation(horizontal / vertical)、shape(square / rounded)、fill(full / auto)与enableIndicator(回退为实心活动触发器样式),通过tabsVariants的scv()配方应用。 - Aria 组合 —
TabsRoot/TabsList/TabsTrigger/TabsContent/TabsIndicator/TabsCompact均可从@vean/aria/tabs导入,用于完全自定义样式构建。
用法
STabs现在将条目迭代、默认触发器/内容组合与指示器渲染委托给 Aria 的TabsCompact。如需无样式、数据驱动的用法,请从@vean/aria/tabs导入TabsCompact。
演示
基础
自定义
API
Aria 层
7Tabs<T>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
class | ClassValue | — | 应用于根元素的额外类名。 |
size | ThemeSize | — | 组件的视觉尺寸。 |
ui | Partial<TabsUi> | — | 带样式的选项卡 UI 插槽,包括用于紧凑型指示器主体的 `indicatorContent`。 |
fill | TabsFill | — | 填充。 |
shape | TabsShape | — | 组件的结构。 |
items | T[] | — | 由该组件渲染的项。 |
enableIndicator | boolean | — | 是否启用指示器。 |
listProps | TabsListProps | — | 转发给列表元素的属性。 |
triggerProps | TabsTriggerProps | — | 转发给触发元素的属性。 |
contentProps | TabsContentProps | — | 转发给内容元素的属性。 |
indicatorProps | TabsIndicatorProps | — | 转发给 indicator 元素的属性。 |
modelValue | T['value'] | null | — | 用于激活的选项卡的受控值。可作为 `v-model` 进行绑定。 |
defaultValue | T['value'] | null | — | 初始渲染时应处于活动状态的标签页的值。当您无需控制标签页的状态时使用此选项 |
activationMode | TabsActivationMode | automatic | Tab 键是自动激活(获得焦点时)还是手动激活(点击时)。 |
unmountOnHide | boolean | `true` | 当值为 `true` 时,该元素将在关闭状态下被卸载。 |
dir | Direction | — | 项目之间的导航方向。 |
loop | boolean | false | 键盘导航是否应循环往复 |
orientation | DataOrientation | — | 该组的取向。主要是为了确保箭头导航能据此正确进行(左右与上下)。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | Exclude<T, undefined> | 当值发生变化时调用的事件处理程序 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
trigger | T & { selected: boolean; } | 触发槽的自定义内容。 |
content | T & { selected: boolean; } | 内容插槽的自定义内容。 |
indicator | — | 指示器插槽的自定义内容。 |
TabsCompact<T>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
items | T[] | — | 由该组件渲染的项。 |
enableIndicator | boolean | — | 是否启用指示器。 |
listProps | TabsListProps | — | 转发给列表元素的属性。 |
triggerProps | TabsTriggerProps | — | 转发给触发元素的属性。 |
contentProps | TabsContentProps | — | 转发给内容元素的属性。 |
indicatorProps | TabsIndicatorProps | — | 转发给 indicator 元素的属性。 |
modelValue | T['value'] | null | — | 用于激活的选项卡的受控值。可作为 `v-model` 进行绑定。 |
defaultValue | T['value'] | null | — | 初始渲染时应处于活动状态的标签页的值。当您无需控制标签页的状态时使用此选项 |
activationMode | TabsActivationMode | automatic | Tab 键是自动激活(获得焦点时)还是手动激活(点击时)。 |
unmountOnHide | boolean | `true` | 当值为 `true` 时,该元素将在关闭状态下被卸载。 |
dir | Direction | — | 项目之间的导航方向。 |
loop | boolean | false | 键盘导航是否应循环往复 |
orientation | DataOrientation | — | 该组的取向。主要是为了确保箭头导航能据此正确进行(左右与上下)。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | Exclude<T, undefined> | 当值发生变化时调用的事件处理程序 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
trigger | T & { selected: boolean; } | 触发槽的自定义内容。 |
content | T & { selected: boolean; } | 内容插槽的自定义内容。 |
indicator | — | 指示器插槽的自定义内容。 |
TabsContent
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
value | DefinedValue | — | 一个将内容与触发器关联起来的唯一值。 |
forceMount | boolean | — | 用于在需要更多控制时强制挂载。
在使用 Vue 动画库控制动画时非常有用。 |
TabsIndicator
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
TabsList
TabsRoot<T>
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
modelValue | T | — | 用于激活的选项卡的受控值。可作为 `v-model` 进行绑定。 |
defaultValue | T | — | 初始渲染时应处于活动状态的标签页的值。当您无需控制标签页的状态时使用此选项 |
activationMode | TabsActivationMode | automatic | Tab 键是自动激活(获得焦点时)还是手动激活(点击时)。 |
unmountOnHide | boolean | `true` | 当值为 `true` 时,该元素将在关闭状态下被卸载。 |
dir | Direction | — | 项目之间的导航方向。 |
loop | boolean | false | 键盘导航是否应循环往复 |
orientation | DataOrientation | — | 该组的取向。主要是为了确保箭头导航能据此正确进行(左右与上下)。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | Exclude<T, undefined> | 当值发生变化时调用的事件处理程序 |
TabsTrigger
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
value | DefinedValue | — | 一个将触发器与内容关联起来的唯一值。 |
disabled | boolean | — | 当值为 `true` 时,禁止用户与该选项卡进行交互。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
备注
架构与竞品对比
| 关注点 | VeanUI | shadcn-vue / Radix Tabs | Ant Design Tabs | Element Plus Tabs |
|---|---|---|---|---|
| Aria / 样式双层分离 | ✅ @vean/aria/tabs + scv() | ✅ Aria 原语 | ❌ 单一包 | ❌ 单一包 |
| 数据驱动 Compact API | ✅ 泛型 TabsCompact<T> + items | ✅ TabList/Tab/TabPanel 部件 | ✅ 配置驱动(items) | ✅ 配置驱动 |
| 受控/非受控 | ✅ modelValue / defaultValue | ✅ modelValue / defaultValue | ✅ activeKey / defaultActiveKey | ✅ v-model |
| 激活模式 | ✅ automatic / manual | ✅ | ❌(始终自动) | ❌(始终自动) |
| 键盘导航 | ✅ roving focus(方向键/Home/End)+ Enter/Space | ✅ roving focus | ✅ 方向键 / Home / End | ✅ 方向键 |
| 禁用标签跳过 | ✅ roving focus 跳过禁用条目 | ✅ | ✅ | ✅ |
| Presence / forceMount 内容 | ✅ unmountOnHide + forceMount + 退出动画 | ✅ forceMount | ❌ | ❌ |
| 动画指示器 | ✅ CSS 变量 + ResizeObserver + RTL 镜像 | ❌(无内置指示器) | ✅ ink bar(line 类型) | ❌ |
| 水平 + 垂直 | ✅ orientation | ✅ | ✅ tabPosition | ✅ tab-position |
| 变体系统 | ✅ size × orientation × shape × fill | ❌(自行定制样式) | ✅ type / size / tabBarGutter | ✅ type / size |
| 自定义插槽 | ✅ trigger / content / indicator | ✅ 部件级 | ✅ label / children | ✅ label / icon |
| ARIA 接线(controls/labelledby) | ✅ 每项自动接线 | ✅ 自动 | ✅ | ✅ |
运行时注意事项
- 聚焦激活 —
activationMode: 'automatic'(默认)下,通过键盘聚焦标签(或ArrowRight/ArrowLeft)即激活;'manual'下仅点击或按Enter/Space激活。 - 指示器测量 — 指示器在列表 ref 挂载后、值/方向变化时与尺寸变化时对活动触发器定位;仅在首次成功测量后渲染。
- RTL 定位 —
dir: 'rtl'下指示器偏移被镜像(list.clientWidth - offsetLeft - offsetWidth),CSSrtl:-translate-x-…翻转位移,指示器始终锚定活动标签。 - 内容 Presence —
unmountOnHide: true时非活动面板内容在退出动画后移除;false时保持挂载但带hidden属性;forceMount将面板保留在 DOM 中,非活动面板仍受hidden约束。 - 受控与非受控 — 提供了
modelValue时,内部写入只发出update:modelValue;DOM 跟随 prop,外部变更自动重新渲染。 - 禁用标签 — 禁用触发器仅保留 roving focus 所需的可聚焦性(
data-disabled,非原生disabled属性),以便方向键跳过;mousedown与键盘激活被阻止。 - ARIA id — 触发器与面板通过确定性 id 关联(
soybean-tabs-trigger-{value}/soybean-tabs-content-{value});aria-controls仅在对应面板注册自身后输出。
FAQ
如何从自动激活切换到手动激活?
设置 activationMode="manual"。手动模式下聚焦标签不再激活;用户通过点击或按 Enter/Space 激活。这符合面板渲染较慢场景下的 WAI-ARIA 标签页模式。
为什么指示器没有立即出现?
指示器需要对活动触发器进行首次测量(偏移 + 尺寸)。它在列表元素挂载或值变化后立即渲染。真实浏览器中 ResizeObserver 通知也会触发重新测量,保证尺寸变化时保持对齐。
unmountOnHide 控制什么?
true(默认)时,切换标签会在退出动画后从 DOM 移除非活动面板内容——利于性能。false 时所有面板保持挂载,但非活动面板带 hidden——利于保留状态(滚动位置、表单输入)。
键盘导航是如何处理的?
列表是 roving-focus 组:ArrowLeft/ArrowRight(垂直模式为 ArrowUp/ArrowDown)移动焦点,Home/End 跳到第一个/最后一个标签,禁用标签被跳过,Enter/Space 激活聚焦的标签。
能完全自定义标签渲染吗?
可以——trigger 插槽接收 scoped { ...item, active },content 插槽接收 { ...item, active },indicator 替换指示器内容。如需完全自定义结构,可从 @vean/aria/tabs 组合 TabsRoot / TabsList / TabsTrigger / TabsContent / TabsIndicator,并通过 provideTabsUi(或 STabs 的 ui prop)注入样式。
需要自己接线 aria-controls 吗?
不需要。每个触发器在面板挂载后自动将 aria-controls 指向其面板 id(soybean-tabs-content-{value}),每个面板也通过 aria-labelledby 指回触发器——条目增删时两者保持同步。