---
title: "Tabs 组件 — zenit Zig UI"
description: "在同一上下文切换平级视图。underline / pill / tab、尺寸、badge、disabled 与 active id 使用同一选择模型。"
url: https://zenit.z.express/zh/components/tabs
language: zh-CN
alternate_en: https://zenit.z.express/components/tabs.md
alternate_es: https://zenit.z.express/es/components/tabs.md
alternate_ja: https://zenit.z.express/ja/components/tabs.md
alternate_ko: https://zenit.z.express/ko/components/tabs.md
alternate_fr: https://zenit.z.express/fr/components/tabs.md
alternate_de: https://zenit.z.express/de/components/tabs.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Tabs

`ui.widgets.Tabs`

在同一上下文切换平级视图。

[Video](https://zenit.z.express/media/stories/tabs.mp4?v=9f6c93c01e)

**BEHAVIOR CONTRACT**

underline / pill / tab、尺寸、badge、disabled 与 active id 使用同一选择模型。

**RECORDED INTERACTION**

Harness 点击 Details，指示器与面板一起切换。

`examples/storybook/stories.zig:2282`

```zig
pub fn buildTabs(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
    const a = cx.allocator;
    const c = try col(cx, 16);
    const items = [_]W.tabs.TabItem{
        .{ .id = "t1", .label_text = "Overview" },
        .{ .id = "t2", .label_text = "Details" },
        .{ .id = "t3", .label_text = "Settings", .badge = 3 },
        .{ .id = "t4", .label_text = "Disabled", .disabled = true },
    };

    const status = try ui.text(cx, "active → (none)", .{ .font_size = 13, .color = light.color.fg_secondary });
    status.meta.ownership.meta.test_id = "story.tabs.status";
    try c.appendChild(a, status);
    const story = try cx.bindState(TabsStory, .{ .status = status });

    // ── 变体 ──
    inline for (.{ W.tabs.TabsVariant.underline, .pill, .tab }, .{ "underline", "pill", "tab" }) |variant, name| {
        try c.appendChild(a, try label(cx, name));
        const tabs_node = try W.Tabs(.{
            .items = &items,
            .variant = variant,
            .on_change = ui.Cx.strHandlerFrom(TabsStory, story, TabsStory.onChange),
        }).mount(scope, cx);
        if (variant == .underline) tabs_node.meta.ownership.meta.test_id = "story.tabs.underline";
        try c.appendChild(a, tabs_node);
    }

    // ── 尺寸 ──
    const size_items = [_]W.tabs.TabItem{
        .{ .id = "a", .label_text = "One" },
        .{ .id = "b", .label_text = "Two" },
        .{ .id = "c", .label_text = "Three" },
    };
    try c.appendChild(a, try label(cx, "Sizes (xs / sm / md / lg)"));
    inline for (.{ W.tabs.TabsSize.xs, .sm, .md, .lg }) |sz| {
        try c.appendChild(a, try W.Tabs(.{ .items = &size_items, .variant = .pill, .size = sz }).mount(scope, cx));
    }
    return c;
}
```

[在 GitHub 查看源码examples/storybook/stories.zig:2282](https://github.com/version-next/zenit/blob/HEAD/examples/storybook/stories.zig#L2282)

这是 Storybook 中该 story 的原样代码（zenit 5f9add5+wip 2026-09-30）。其中 `W` 即 `ui.widgets`，`col` / `row` / `label` 是 Storybook 内的布局小工具。

### TabsProps

`ui.widgets.Tabs`

[src/ui/components/tabs/mod.zig:306](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/tabs/mod.zig#L306)

```zig
fn Tabs(props: TabsProps) TabsBuilder
```

| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `items` | `[]const TabItem` | — | — |
| `active_id` | `?[]const u8` | `null` | — |
| `default_active_id` | `?[]const u8` | `null` | — |
| `variant` | `TabsVariant` `.underline` `.pill` `.tab` | `.underline` | — |
| `size` | `TabsSize` | `.md` | — |
| `closable` | `bool` | `false` | — |
| `on_change` | `?core.HandlerRef` | `null` | 2026-07-31 并轨：统一 ?core.HandlerRef，用 invokeWithStr 触发 （payload = tab id）。 |
| `on_close` | `?core.HandlerRef` | `null` | — |
| `context` | `?*anyopaque` | `null` | — |
| `state_id` | `?u64` | `null` | — |

### TabPanelProps

`ui.widgets.TabPanel`

[src/ui/components/tabs/mod.zig:709](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/tabs/mod.zig#L709)

```zig
fn TabPanel(props: TabPanelProps) TabPanelBuilder
```

| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `id` | `[]const u8` | — | — |
| `active` | `bool` | `true` | — |

### \*Node

`ui.widgets.Tabs`

mount 直接返回 `*Node`，把它 append 到父节点即可。

### TabPanelBuilder

`ui.widgets.TabPanel`

mount 直接返回 `TabPanelBuilder`，把它 append 到父节点即可。
