---
title: "Notification-Komponente — zenit Zig UI"
description: "Nicht blockierende Benachrichtigungen: neun Arten, Fortschritt, Aktionen, Stapelung und acht Andockpositionen."
url: https://zenit.z.express/de/components/notification
language: de
alternate_en: https://zenit.z.express/components/notification.md
alternate_zh: https://zenit.z.express/zh/components/notification.md
alternate_es: https://zenit.z.express/es/components/notification.md
alternate_ja: https://zenit.z.express/ja/components/notification.md
alternate_ko: https://zenit.z.express/ko/components/notification.md
alternate_fr: https://zenit.z.express/fr/components/notification.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Notification

`ui.widgets.Notifier`

Nicht blockierende Benachrichtigungen: neun Arten, Fortschritt, Aktionen, Stapelung und acht Andockpositionen.

[Video](https://zenit.z.express/media/stories/notification.mp4?v=5f8cadb85b)

**BEHAVIOR CONTRACT**

Der Notifier verwaltet den Portal-Stapel, Ein- / Ausblenden, Aufklappen bei Hover mit pausierten Timern und hält den Stapel beim Größenändern am Anker; die Karten nutzen ein weißes Glasmaterial.

**RECORDED INTERACTION**

Der Harness löst alle neun Arten aus (die Fortschrittskarte läuft bis zum Ende), feuert vier auf einmal zu einem Stapel, klappt ihn per Hover auf, schließt die mittlere Karte und räumt dann alle ab.

[Video](https://zenit.z.express/media/stories/notification-positions.mp4?v=2df7ff87b6)

Acht Andockpositionen: Ein Positionswechsel verschiebt Anker, Wachstums- und Einblendrichtung, ohne Karten neu aufzubauen. Ganzes Fenster im Bild, da die linken Positionen am Fensterrand anliegen.

[Zum Leitfaden →](https://zenit.z.express/de/docs/advanced/notifications)

`examples/storybook/notification_story.zig:290`

```zig
pub fn build(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
    const t = cx.tokens;
    const root = try ui.box(cx, .{ .width = .{ .grow = .{} }, .direction = .column, .gap = 12 }, .{});

    const n = try Notifier.init(scope, cx, .{
        .position = .bottom_center,
        // storybook 顶部 56px 工具栏。
        .content_insets = .{ .top = 56 },
        .strings = zh_strings,
    });
    const status = try ui.text(cx, "event · idle", .{ .font_size = 12, .font_weight = 550, .color = t.color.fg_secondary });
    status.meta.ownership.meta.test_id = "story.notify.status";
    const state = try cx.bindState(Story, .{ .n = n, .cx = cx, .status = status });
    n.setListener(.{ .context = @ptrCast(state), .callback = Story.onEvent });
    root.meta.per_frame.hooks.slots.anim_state = @ptrCast(state);
    root.meta.per_frame.hooks.before_render.main = storyHook;

    const kinds = try controlRow(cx);
    try addButton(scope, cx, kinds, state, "成功 Success", "story.notify.btn.success", Story.success, .secondary);
    try addButton(scope, cx, kinds, state, "错误 Error", "story.notify.btn.error", Story.failure, .secondary);
    try addButton(scope, cx, kinds, state, "警告 Warning", "story.notify.btn.warning", Story.warning, .secondary);
    try addButton(scope, cx, kinds, state, "进行中 Progress", "story.notify.btn.progress", Story.progress, .secondary);
    try addButton(scope, cx, kinds, state, "需要操作 Action", "story.notify.btn.action", Story.action, .secondary);
    try addButton(scope, cx, kinds, state, "人员消息 Person", "story.notify.btn.person", Story.person, .secondary);
    try addButton(scope, cx, kinds, state, "撤销 Undo", "story.notify.btn.undo", Story.undo, .secondary);
    try addButton(scope, cx, kinds, state, "静默 Quiet", "story.notify.btn.quiet", Story.quiet, .secondary);
    try addButton(scope, cx, kinds, state, "汇总 Digest", "story.notify.btn.digest", Story.digest, .secondary);
    try root.appendChild(cx.allocator, try group(cx, try sectionLabel(cx, "九种类型 Nine kinds", "卡片 392 宽 · 圆角 18 · 液态玻璃；按钮在分隔线下方，中性灰底不上色。"), kinds));

    const stack = try controlRow(cx);
    try addButton(scope, cx, stack, state, "连发 4 条", "story.notify.btn.burst", Story.burst, .primary);
    try addButton(scope, cx, stack, state, "全部收起", "story.notify.btn.clear", Story.clear, .ghost);
    try stack.appendChild(cx.allocator, status);
    try root.appendChild(cx.allocator, try group(cx, try sectionLabel(cx, "堆叠 Stack", "折叠露边 9px · 悬停展开并暂停所有计时 · 删除中间一条平滑补位。"), stack));

    const pos = try controlRow(cx);
    try addButton(scope, cx, pos, state, "上左", "story.notify.pos.tl", Story.posTL, .secondary);
    try addButton(scope, cx, pos, state, "上中", "story.notify.pos.tc", Story.posTC, .secondary);
    try addButton(scope, cx, pos, state, "上右", "story.notify.pos.tr", Story.posTR, .secondary);
    try addButton(scope, cx, pos, state, "右中", "story.notify.pos.rc", Story.posRC, .secondary);
    try addButton(scope, cx, pos, state, "右下", "story.notify.pos.br", Story.posBR, .secondary);
    try addButton(scope, cx, pos, state, "下中", "story.notify.pos.bc", Story.posBC, .secondary);
    try addButton(scope, cx, pos, state, "下左", "story.notify.pos.bl", Story.posBL, .secondary);
    try addButton(scope, cx, pos, state, "左中", "story.notify.pos.lc", Story.posLC, .secondary);
    try root.appendChild(cx.allocator, try group(cx, try sectionLabel(cx, "八个停靠位置 Positions", "位置只改锚点、增长方向、入场位移方向；切换不重建卡片。"), pos));

    // 玻璃需要有内容的背景才看得出来：模拟卡片身后的正文（设计稿里提醒浮在编辑器正文上）。
    const backdrop = try ui.box(cx, .{ .width = .{ .grow = .{} }, .direction = .column, .gap = 0 }, .{});
    backdrop.meta.ownership.meta.test_id = "story.notify.backdrop";
    const tints = [_]u24{ 0xE4572E, 0x17BEBB, 0xFFC914, 0x2E282A, 0x76B041, 0x8059BB };
    var li: usize = 0;
    while (li < 36) : (li += 1) {
        const line = try ui.box(cx, .{ .width = .{ .grow = .{} }, .height = .{ .px = 22 }, .direction = .row, .gap = 28, .padding = ui.Padding.symmetric(0, 12), .align_items = .center, .background = ui.arb.hex(tints[li % tints.len]) }, .{});
        var k: usize = 0;
        while (k < 6) : (k += 1) try line.appendChild(cx.allocator, try ui.text(cx, "The foundation of any great editor", .{ .font_size = 13, .color = ui.arb.hex(0xFFFFFF) }));
        try backdrop.appendChild(cx.allocator, line);
    }
    try root.appendChild(cx.allocator, backdrop);

    if (!n.portaled) try root.appendChild(cx.allocator, n.container);
    return root;
}
```

[Quellcode auf GitHub ansehenexamples/storybook/notification\_story.zig:290](https://github.com/version-next/zenit/blob/HEAD/examples/storybook/notification_story.zig#L290)

Der unveränderte Quellcode der Story aus dem Storybook (zenit 5f9add5+wip 2026-09-30). `W` steht für `ui.widgets`; `col` / `row` / `label` sind kleine Layout-Helfer des Storybooks.

### Notification

`ui.widgets.Notification`

[src/ui/components/notification/mod.zig:51](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/notification/mod.zig#L51)

| Feld | Typ | Standard | Beschreibung |
| --- | --- | --- | --- |
| `id` | `?Id` | `null` | 已存在的 id 会原地转换（保留 born，不重放入场）。 |
| `kind` | `Kind` `.default` `.progress` `.undo` | `.default` | — |
| `tone` | `Tone` `.success` `.@"error"` `.warning` `.info` `.violet` `.quiet` | `.info` | — |
| `title` | `[]const u8` | `""` | — |
| `body` | `[]const u8` | `""` | — |
| `glyph` | `?svg_assets.Asset` | `null` | 覆盖语义色调的默认字形（✓ / ✕ / △ …）。 |
| `lead` | `?Lead` `.chip` `.spinner` `.ring` `.avatar` `.dot` | `null` | 覆盖行首图标位的默认呈现（由 kind / tone / avatar 推导）。 |
| `avatar` | `[]const u8` | `""` | 人员消息的头像首字。 |
| `actions` | `[]const Action` | `&.{}` | 分隔线下方的等宽按钮（最多 2 个）。undo 类型缺省为「撤销」。 |
| `reply` | `bool` | `false` | 内联回复框（Enter 或「发送」提交）。 |
| `quote` | `?[]const u8` | `null` | 回复已发出后的引用行（原地转换用）。 |
| `progress` | `?Progress` | `null` | — |
| `sticky` | `bool` | `false` | 常驻：不自动消失。 |
| `duration_ms` | `?u32` | `null` | 覆盖类型默认停留时长。 |
| `life_bar` | `?bool` | `null` | 覆盖生命条显示（默认：有时限且非 progress 时显示）。 |

### Options

`ui.widgets.NotifierOptions`

[src/ui/components/notification/mod.zig:112](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/notification/mod.zig#L112)

| Feld | Typ | Standard | Beschreibung |
| --- | --- | --- | --- |
| `position` | `Position` `.top_left` `.top_center` `.top_right` `.right_center` `.bottom_right` `.bottom_center` `.bottom_left` `.left_center` | `.bottom_center` | — |
| `max_visible` | `u8` | `Spec.default_max_visible` | 展开时最多显示条数（2–6）。 |
| `expand_on_hover` | `bool` | `true` | false：悬停只暂停计时，不展开。 |
| `content_insets` | `Insets` | `.{}` | 内容区相对窗口的内缩（标题栏 / 状态栏），停靠边距 22 从这里起算。 |
| `width` | `f32` | `Spec.card_width` | — |
| `strings` | `Strings` | `.{}` | — |
| `listener` | `?Listener` | `null` | — |

### Notifier

`ui.widgets.Notifier`

[src/ui/components/notification/mod.zig:132](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/notification/mod.zig#L132)

```zig
fn init(scope: *Scope, cx: *Cx, options: Options) !*Notifier
```

```zig
fn show(self: *Notifier, n: Notification) !Id
```

弹出一条提醒；id 已存在时原地转换。同屏超过 6 条时最旧一条走正常退场。

```zig
fn update(self: *Notifier, id: Id, n: Notification) !void
```

原地转换：改写 kind / tone / title / body，重置 life，换图标 —— 不销毁、 不重建卡片，born 不变，不重放入场（16.6）。

```zig
fn setProgress(self: *Notifier, id: Id, value: f32, label: ?[]const u8, count_text: ?[]const u8) void
```

更新进度卡的进度（不重建内容层）。

```zig
fn dismiss(self: *Notifier, id: Id) void
```

```zig
fn dismissAll(self: *Notifier) void
```

```zig
fn setPosition(self: *Notifier, position: Position) void
```

位置切换不重建卡片，只改锚点参数；退场中的沿新方向继续完成退场。

```zig
fn setListener(self: *Notifier, listener: ?Listener) void
```

```zig
fn setExpandOnHover(self: *Notifier, on: bool) void
```

```zig
fn setMaxVisible(self: *Notifier, n: u8) void
```

```zig
fn count(self: *const Notifier) usize
```

在场（未退场）的条数。

```zig
fn isExpanded(self: *const Notifier) bool
```

```zig
fn isPaused(self: *const Notifier) bool
```

```zig
fn cardForId(self: *Notifier, id: Id) ?*Card
```

取卡片（测试 id、埋点、截图断言用）。

```zig
fn clearAll(self: *Notifier) void
```

用户「全部清除」：所有在屏提醒按用户关闭退场（逐条回调 `.dismissed`）。

```zig
fn replyFailed(self: *Notifier, id: Id, title: []const u8, body: []const u8) void
```

回复发送失败：原地转为错误类型并把刚才的内容恢复进回复框。
