---
title: "UI-Baum & Layout — zenit Zig UI Doku"
description: "Bauen Sie zenits räumliches Modell mit Node-Konstruktoren und Layout im Flex-Stil auf."
url: https://zenit.z.express/de/docs/guide/ui-tree
language: de
alternate_en: https://zenit.z.express/docs/guide/ui-tree.md
alternate_zh: https://zenit.z.express/zh/docs/guide/ui-tree.md
alternate_es: https://zenit.z.express/es/docs/guide/ui-tree.md
alternate_ja: https://zenit.z.express/ja/docs/guide/ui-tree.md
alternate_ko: https://zenit.z.express/ko/docs/guide/ui-tree.md
alternate_fr: https://zenit.z.express/fr/docs/guide/ui-tree.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# UI-Baum und Layout

Bauen Sie zenits räumliches Modell mit Node-Konstruktoren und Layout im Flex-Stil auf.

## Mentales Modell

Ihre Mount-Funktion baut einen echten, dauerhaften Baum aus `*ui.Node`s – genau einmal. Container bestimmen, wie Kinder angeordnet werden; Blätter tragen Text, Bilder oder Icons. In jedem späteren Frame steigt das Framework nur ab dirty Nodes wieder in die Pipeline ein: Layout, Paint in Display Items, Übergabe an Metal.

FRAME PIPELINE

Jedes Style-Feld hat eine Dirty-Stufe. Eine Hintergrundänderung erzeugt nur den Paint neu; eine Breitenänderung layoutet auch den Parent neu. Bei sauberem Baum und ohne laufende Animation wird die gesamte GPU-Übermittlung übersprungen.

## Konstruktoren

| Konstruktor | Verwendung |
| --- | --- |
| `ui.box` | Allgemeiner Container mit voller Kontrolle über Richtung, Größe, Abstände und Hintergrund. Standard: column, Breite und Höhe fit |
| `ui.hstack / ui.vstack` | Eine Box mit fest auf row / column gesetzter Richtung |
| `ui.text / ui.textFmt` | Statischer Text / formatierter Text, der Signals abonniert: `textFmt(cx, scope, fmt, .{signals}, props)` |
| `ui.icon / ui.iconTint / ui.svg / ui.image` | Vektor-Icons, eingefärbte Icons, SVG und Bitmap-Texturen |
| `ui.grid` | Ein Grid-Container, der auf Spalten- / Zeilen-Tracks auslegt |
| `ui.spacer` | Flexibler Leerraum, grow in beiden Achsen |
| `ui.clickable` | Hängt einen `on_click`\-Handler an einen beliebigen Node und gibt ihn zurück |
| `ui.boxStyled / hstackStyled / vstackStyled / textStyled` | Nehmen eine benannte Style-Funktion entgegen und spielen sie bei Theme-Wechsel erneut ab – siehe [Styling und Themes](https://zenit.z.express/de/docs/guide/styling) |

> TIP
> 
> **Greifen Sie zuerst zu einem Widget.** Buttons, Eingabefelder, Listen und andere Controls in `ui.widgets` bringen Zustand, Fokus und Tastatursemantik bereits mit; die Konstruktoren sind für die Struktur dazwischen. Stöbern Sie auf der [Komponenten-Seite](https://zenit.z.express/de/components).

## Ein Layout-Beispiel

Erstellen Sie den Container und hängen Sie dann Kinder mit `appendChild` an. Style-Werte kommen zuerst aus `cx.tokens`, sodass Theme-Wechsel und Abstandsänderungen keine Jagd nach Literalen bedeuten.

`card.zig`

```zig
const card = try ui.vstack(cx, .{
    .width = .fixed(360),
    .gap = cx.tokens.space._4,
    .padding = ui.Padding.all(cx.tokens.space._6),
    .background = cx.tokens.color.bg_secondary,
    .corner_radius = cx.tokens.radius.xl,
    .align_items = .stretch,
}, .{});

const header = try ui.hstack(cx, .{
    .gap = cx.tokens.space._2,
    .align_items = .center,
}, .{});

try header.appendChild(cx.allocator, try ui.iconTint(
    cx,
    ui.icons.star,
    cx.tokens.color.accent,
    .{ .width = .fixed(18), .height = .fixed(18) },
));
try header.appendChild(cx.allocator, try ui.text(cx, "Overview", .{}));
try header.appendChild(cx.allocator, try ui.spacer(cx));
try card.appendChild(cx.allocator, header);
```

Kinder lassen sich bei der Konstruktion auch als Tupel übergeben; `ui.Padding.symmetric(v, h)` setzt vertikales und horizontales Padding getrennt.

`row.zig`

```zig
// Children can also be passed as a tuple at construction time.
const row = try ui.hstack(cx, .{
    .width = .fill(),
    .padding = ui.Padding.symmetric(cx.tokens.space._2, cx.tokens.space._4),
    .gap = cx.tokens.space._3,
}, .{
    try ui.text(cx, "Name", .{}),
    try ui.spacer(cx),
    try ui.text(cx, "42", .{ .color = cx.tokens.color.fg_secondary }),
});

// Any node can take a click handler.
// (model: *Model from cx.bindState; Model.select is fn (*Model) void)
_ = ui.clickable(row, cx.on(Model, model, Model.select));
```

[Video](https://zenit.z.express/media/stories/stack.mp4?v=f9618b3adf)

Die echte [Stack](https://zenit.z.express/de/components/stack)\-Story: horizontale, vertikale und verschachtelte Container werden alle über dieselben Regeln für rect, gap, align und sizing aufgelöst.

## Größen und Koordinaten

Breite und Höhe sind eine `ui.Sizing`\-Union; jedes ihrer vier Mitglieder hat eine Kurzform:

| Kurzform | Mitglied | Bedeutung |
| --- | --- | --- |
| `.fixed(120)` | `.{ .px = 120 }` | Feste Pixel |
| `.fill()` | `.{ .grow = .{} }` | Nimmt den verbleibenden Platz; `grow` trägt eine `min` / `max`\-Nutzlast, z. B. `.{ .grow = .{ .min = 200 } }` |
| `.pct(50)` | `.{ .percent = 50 }` | Prozent (0–100) der Content-Box des Parents, ohne Padding |
| — | `.{ .fit = .{} }` | Durch den Inhalt bestimmt; Standard von box, ebenfalls mit min / max |

SIZING

Ändert der Parent seine Größe, bleibt .fixed unverändert, .pct folgt der Content-Box proportional, und .fill nimmt alles, was nach festen Größen, Prozentwerten und Gaps übrig bleibt.

Layout-Ergebnisse werden nicht als Node-Feld gespeichert. `node.rectFromWorldOrFallback()` liefert das Rect aus dem letzten Layout-Durchlauf, relativ zur linken oberen Ecke des Parents; für Fensterkoordinaten nutzen Sie `node.globalRect()`, das Positionen, Translate- und Sticky-Offsets entlang der Vorfahrenkette aufsummiert.

```zig
// Parent-relative rect from the last layout pass.
const local = node.rectFromWorldOrFallback();

// Window coordinates: accumulates ancestors, translate and sticky offsets.
const screen = node.globalRect();
std.log.info("{d}x{d} at ({d}, {d})", .{ screen.w, screen.h, screen.x, screen.y });
_ = local;
```

[Video](https://zenit.z.express/media/stories/layoutbox.mp4?v=4d3c4ba31b)

Die LayoutBox-Story: Box-Modell, Prozentwerte, absolute Positionierung, Flex / Grid und Clipping, jeweils mit Hilfslinien für Padding-Box und Content-Box.

> TIP
> 
> **Prüfen Sie zuerst den Parent.** Wirken Ausrichtung oder Klickbereiche falsch, liegt die Ursache meist bei Padding, Gap, Direction oder Sizing des Parents, nicht beim Blatt. Während der Entwicklung fügt `ui.devtools.overlay.attach(cx, scope, root, .{})` einen Hover-Inspektor hinzu, der das Rect jedes Nodes umrandet – siehe [DevTools](https://zenit.z.express/de/docs/advanced/devtools).

## Nodes aktualisieren

Wenn Sie einen Node direkt ändern, muss das Framework wissen, welcher Teil der Pipeline erneut laufen soll. Bevorzugen Sie `setStyle`: Es wählt die Dirty-Stufe zur Compile-Zeit anhand des Felds, ein manuelles `markLayoutDirty` / `markRenderDirty` entfällt.

| Stufe | Typische Felder | Nächster Frame |
| --- | --- | --- |
| `.sizing` | `width, height` | Relayout von Node und Parent |
| `.layout` | `padding, margin, gap, direction, justify, align_items, min/max_*` | Relayout dieses Nodes |
| `.interaction` | `opacity, translate_*, corner_radius, border, z_index, cursor` | Hit-Index aktualisieren und neu zeichnen |
| `.render` | `background, shadow, gradient, outline, text_color` | Nur Paint neu erzeugen |
| `.none` | `tab_index, layout_isolation` | Keine Frame-Arbeit |

`update.zig`

```zig
// setStyle picks the dirty level from the field at compile time.
node.setStyle(cx.allocator, .width, .fixed(240)); // sizing
node.setStyle(cx.allocator, .gap, 12);             // layout
node.setStyle(cx.allocator, .background, next);    // render

// Low-frequency fields live in StyleExt and need a real allocator.
node.setStyle(cx.allocator, .z_index, 10);

// Replace text: dupes the content, frees the previous owned copy,
// and re-measures only if the content actually changed.
try node.setTextContent(cx.allocator, "Updated");

// Hide without unmounting: display:none leaves layout, paint,
// hit-testing, Tab order and the a11y tree; state stays alive.
panel.setDisplay(.none);
panel.setDisplay(.flex); // back, same nodes

// Recolor an icon node whether it is icon-table or image (svgTint) backed.
_ = icon.setTint(cx.tokens.color.accent); // false: node has neither
```

`setText` vergleicht die Signaturen von altem und neuem Text: sizing-dirty, wenn sich die Messung ändert, render-dirty, wenn sich nur das Aussehen ändert, gar nichts bei Gleichheit. Nach einer Textänderung ist kein `markRenderDirty` nötig.

Um einen Teilbaum vorübergehend auszublenden, rufen Sie `node.setDisplay(.none)` auf (oder setzen Sie `.display = .none` in einem BoxStyle): Der Node und sein Teilbaum belegen keinen Platz, zählen keinen Gap und werden nicht gezeichnet; außerdem verlassen sie Hit-Testing, Tab-Reihenfolge und Accessibility-Baum. Nodes und Zustand bleiben erhalten, und `.flex` holt sie zurück – ohne Unmount und Neuaufbau. `setDisplay` markiert das Layout des Nodes und seines Parents selbst als dirty. Opacity 0 allein stoppt zwar ebenfalls das Hit-Testing, doch der Node belegt weiterhin Platz und bleibt in der Tab-Reihenfolge.

> WARNING
> 
> **Text mit setTextContent ersetzen.** Rufen Sie nicht `getText()` auf, um `content` auf einen temporären String zu richten und ihn per `setText` zurückzuschreiben: `setText` kopiert den Inhalt nicht, und war der alte Inhalt owned, wird das `owned`\-Flag mitkopiert und mit dem neuen Slice verwechselt. `setTextContent` dupliziert den neuen Inhalt, markiert ihn als owned und überlässt dem Framework die Freigabe der vorigen Kopie. Wenn Sie `TextProps` selbst bauen, verwenden Sie `props.setContent(cx.allocator, src)`: bis 16 Bytes landen ohne Allokation im Inline-Buffer, längere Inhalte werden als owned dupliziert. `setInlineContent` gibt über 16 Bytes `error.InlineContentTooLong` zurück, statt still zu kürzen.

> NOTE
> 
> **Selten genutzte Felder brauchen einen Allocator.** Felder wie `shadow`, `z_index`, `corner_radius` und `min_width` liegen in einer bei Bedarf allokierten StyleExt. Einen literalen `null`\-Allocator dafür zu übergeben, ist ein Compile-Fehler; im Zweifel übergeben Sie `cx.allocator`.
