---
title: "Composant GlassChrome — zenit Zig UI"
description: "Chrome d'application : barre de titre et barre d'outils en verre au-dessus d'un contenu qui défile."
url: https://zenit.z.express/fr/components/glasschrome
language: fr
alternate_en: https://zenit.z.express/components/glasschrome.md
alternate_zh: https://zenit.z.express/zh/components/glasschrome.md
alternate_es: https://zenit.z.express/es/components/glasschrome.md
alternate_ja: https://zenit.z.express/ja/components/glasschrome.md
alternate_ko: https://zenit.z.express/ko/components/glasschrome.md
alternate_de: https://zenit.z.express/de/components/glasschrome.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# GlassChrome

`ui.widgets.GlassBox`

Chrome d'application : barre de titre et barre d'outils en verre au-dessus d'un contenu qui défile.

[Video](https://zenit.z.express/media/stories/glasschrome.mp4?v=198f0a54f6)

**BEHAVIOR CONTRACT**

Le rayon de flou, les calques et le contenu défilé sont composés dans la même image.

**RECORDED INTERACTION**

Le harness clique sur Scroll +90 ; le contenu défile sous le verre.

`examples/storybook/stories.zig:3878`

```zig
pub fn buildGlassChrome(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
    const a = cx.allocator;
    const c = try col(cx, 16);
    const STAGE_W: f32 = 620;

    // ── 1. header 浮在滚动画布之上（画板详情页主场景）──
    try c.appendChild(a, try label(cx, "App header — pure-blur glass pills floating over a scrolling canvas"));
    const stage = try ui.box(cx, .{
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 200 },
        .position = .relative,
        .overflow_hidden = true,
    }, .{});
    (try stage.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);

    const sa = try W.mountScrollArea(.{ .width = STAGE_W, .height = 200, .direction = .vertical }, scope, cx);
    // 三屏画布内容，滚动时玻璃下的背景持续变化
    for (0..3) |_| try sa.content.appendChild(a, try chromeCanvas(cx, STAGE_W, 260));
    sa.content.meta.ownership.meta.test_id = "story.glasschrome.canvas";
    try stage.appendChild(a, sa.container);

    // 真实应用的分层：76px 渐进 blur strip 贴顶（header 行本身**无**玻璃），
    // 玻璃胶囊浮在 strip 之上。所以这里必须先挂 strip 再挂 header 行。
    const edge_strip = try ui.box(cx, .{
        .position = .absolute,
        .width = .{ .px = STAGE_W },
        .height = .{ .px = CHROME_EDGE_H },
    }, .{});
    edge_strip.meta.ownership.meta.test_id = "story.glasschrome.edge";
    const sext = try edge_strip.style.ensureExtFallible(a);
    sext.inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
    sext.glass = progressiveEdgeGlass();
    try stage.appendChild(a, edge_strip);

    // header wrapper：绝对定位 + translate_y = ISLAND_M（同款）。
    // 行本身透明——只有里面的胶囊有玻璃。
    const hdr_wrap = try ui.box(cx, .{
        .position = .absolute,
        .width = .{ .px = STAGE_W },
        .direction = .row,
    }, .{});
    (try hdr_wrap.style.ensureExtFallible(a)).inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
    hdr_wrap.setStyle(null, .translate_y, CHROME_ISLAND_M);
    try hdr_wrap.appendChild(a, try chromeHeaderRow(cx, scope, STAGE_W));
    try stage.appendChild(a, hdr_wrap);
    try c.appendChild(a, stage);

    const st = try cx.bindState(GlassChromeStory, .{ .scroll = sa.state, .content = sa.content });
    const controls = try ui.box(cx, .{ .direction = .row, .gap = 10 }, .{});
    const down = try W.Button(.{ .label = "Scroll +90", .on_click = cx.on(GlassChromeStory, st, GlassChromeStory.scrollDown) }).mount(scope, cx);
    down.meta.ownership.meta.test_id = "story.glasschrome.scrolldown";
    try controls.appendChild(a, down);
    const up = try W.Button(.{ .label = "Scroll −90", .on_click = cx.on(GlassChromeStory, st, GlassChromeStory.scrollUp) }).mount(scope, cx);
    up.meta.ownership.meta.test_id = "story.glasschrome.scrollup";
    try controls.appendChild(a, up);
    try c.appendChild(a, controls);

    // ── 2. 渐进式毛玻璃 scroll edge（progressiveBlurEdge）──
    // 顶部 30% 满 blur → 100% 处收敛到 0。验收点：不该出现下缘切线、
    // 白纱、或接缝——这些是这一档参数特有的失败模式。
    try c.appendChild(a, try label(cx, "Progressive blur scroll edge — full blur at top, fading to none (no seam, no bottom hairline)"));
    const edge_stage = try ui.box(cx, .{
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 150 },
        .position = .relative,
        .overflow_hidden = true,
    }, .{});
    (try edge_stage.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);
    const esa = try W.mountScrollArea(.{ .width = STAGE_W, .height = 150, .direction = .vertical }, scope, cx);
    for (0..3) |_| try esa.content.appendChild(a, try chromeCanvas(cx, STAGE_W, 260));
    try edge_stage.appendChild(a, esa.container);

    const edge = try ui.box(cx, .{
        .position = .absolute,
        .width = .{ .px = STAGE_W },
        .height = .{ .px = CHROME_EDGE_H },
    }, .{});
    edge.meta.ownership.meta.test_id = "story.glasschrome.edgebare";
    const eext = try edge.style.ensureExtFallible(a);
    eext.inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
    eext.glass = progressiveEdgeGlass();
    try edge_stage.appendChild(a, edge);
    try c.appendChild(a, edge_stage);

    // ── 3. 强度对照阶梯：同一底、只变 backdrop_blur ──
    // 用于判断 20 这档在浅底上到底够不够"磨砂"——单看一个说不清。
    try c.appendChild(a, try label(cx, "backdrop_blur ladder (transparent fill) — 0 / 8 / 20 (app) / 40"));
    const ladder_stage = try ui.box(cx, .{
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 150 },
        .position = .relative,
        .overflow_hidden = true,
    }, .{});
    (try ladder_stage.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);
    try ladder_stage.appendChild(a, try chromeCanvas(cx, STAGE_W, 150));
    const ladder_row = try ui.box(cx, .{
        .position = .absolute,
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 150 },
        .direction = .row,
        .align_items = .center,
        .justify = .space_evenly,
    }, .{});
    (try ladder_row.style.ensureExtFallible(a)).inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
    inline for (.{ 0, 8, 20, 40 }, 0..) |blur, i| {
        const pill = try chromePill(cx, 118, CHROME_PALETTE_H, 0);
        // 诊断：背景全透明，排除 0xE0 白底遮住 blur 的可能
        pill.setStyle(null, .background, ui.Color.rgba(0, 0, 0, 0));
        (try pill.style.ensureExtFallible(a)).glass = .{ .backdrop_blur = blur, .glass_intensity = 0 };
        var buf: [16]u8 = undefined;
        const txt = try std.fmt.bufPrint(&buf, "blur {d}", .{blur});
        try pill.appendChild(a, try ui.text(cx, txt, .{
            .font_size = 12,
            .font_weight = 600,
            .color = chrome_col.ink2,
        }));
        _ = i;
        try ladder_row.appendChild(a, pill);
    }
    try ladder_stage.appendChild(a, ladder_row);
    try c.appendChild(a, ladder_stage);

    // ── 4. 填充不透明度阶梯：blur 固定 20，只变白底 alpha ──
    // 关键对照：应用里用的 0xE0（88% 白）只剩 12% 透光，blur 再大也看不见
    // ——玻璃钱花了但没有视觉收益。0x99/0xB0 才真读得出磨砂。
    try c.appendChild(a, try label(cx, "fill alpha ladder at fixed blur 20 — 0x66 / 0x99 / 0xB0 / 0xE0 (app): blur is invisible past ~0xB0"));
    const alpha_stage = try ui.box(cx, .{
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 150 },
        .position = .relative,
        .overflow_hidden = true,
    }, .{});
    (try alpha_stage.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);
    try alpha_stage.appendChild(a, try chromeCanvas(cx, STAGE_W, 150));
    const alpha_row = try ui.box(cx, .{
        .position = .absolute,
        .width = .{ .px = STAGE_W },
        .height = .{ .px = 150 },
        .direction = .row,
        .align_items = .center,
        .justify = .space_evenly,
    }, .{});
    (try alpha_row.style.ensureExtFallible(a)).inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
    inline for (.{ 0x66, 0x99, 0xB0, 0xE0 }) |alpha| {
        const pill = try chromePill(cx, 118, CHROME_PALETTE_H, 0);
        pill.setStyle(null, .background, ui.Color.rgba(0xFF, 0xFF, 0xFF, alpha));
        var buf: [16]u8 = undefined;
        const txt = try std.fmt.bufPrint(&buf, "0x{X:0>2}", .{alpha});
        try pill.appendChild(a, try ui.text(cx, txt, .{
            .font_size = 12,
            .font_weight = 600,
            .color = chrome_col.ink2,
        }));
        try alpha_row.appendChild(a, pill);
    }
    try alpha_stage.appendChild(a, alpha_row);
    try c.appendChild(a, alpha_stage);

    return c;
}
```

[Voir le code sur GitHubexamples/storybook/stories.zig:3878](https://github.com/version-next/zenit/blob/HEAD/examples/storybook/stories.zig#L3878)

Le code source tel quel de la story dans le Storybook (zenit 5f9add5+wip 2026-09-30). `W` désigne `ui.widgets` ; `col` / `row` / `label` sont de petits helpers de mise en page du Storybook.

### GlassBoxProps

`ui.widgets.GlassBox`

[src/ui/components/glass\_box/mod.zig:48](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/glass_box/mod.zig#L48)

```zig
fn GlassBox(props: GlassBoxProps) GlassBoxBuilder
```

| Champ | Type | Défaut | Description |
| --- | --- | --- | --- |
| `title` | `?[]const u8` | `null` | — |
| `subtitle` | `?[]const u8` | `null` | — |
| `width` | `?f32` | `null` | — |
| `height` | `?f32` | `null` | — |
| `padding` | `?Padding` | `null` | — |
| `body_gap` | `f32` | `12` | — |
| `radius` | `f32` | `18` | — |
| `readability` | `GlassBoxReadability` `.balanced` `.preferred` `.maximum` | `.preferred` | — |
| `emphasis` | `GlassBoxEmphasis` `.subtle` `.regular` `.prominent` | `.regular` | — |
| `variant` | `GlassBoxVariant` `.regular` `.clear` | `.regular` | — |
| `tint` | `?Color` | `null` | — |
| `interactive` | `bool` | `false` | Apple Liquid Glass interactive：hover 提亮高光、press 玻璃增厚 + 微放大。 |
| `scroll_edge` | `?ScrollEdgeBinding` | `null` | scroll-edge effect：绑定 ScrollArea 后，内容滚过玻璃边缘时出现渐进 模糊/压暗带（随滚动位置实时驱动 + 插值平滑）。 |

### \*Node

`ui.widgets.GlassBox`

mount renvoie directement `*Node` : ajoutez-le à un parent.
