---
title: "Eigene Themes — zenit Zig UI Doku"
description: "Ein Theme ist ein einziger ThemeTokens-Wert — Farben, Abstände, Radien, Schriftgrößen und Control-Maße."
url: https://zenit.z.express/de/docs/guide/theming
language: de
alternate_en: https://zenit.z.express/docs/guide/theming.md
alternate_zh: https://zenit.z.express/zh/docs/guide/theming.md
alternate_es: https://zenit.z.express/es/docs/guide/theming.md
alternate_ja: https://zenit.z.express/ja/docs/guide/theming.md
alternate_ko: https://zenit.z.express/ko/docs/guide/theming.md
alternate_fr: https://zenit.z.express/fr/docs/guide/theming.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Eigene Themes

Ein Theme ist ein einziger ThemeTokens-Wert — Farben, Abstände, Radien, Schriftgrößen und Control-Maße. Kopieren Sie ein eingebautes Preset, ändern Sie die Felder, die Ihre Marke tragen, installieren Sie es mit cx.setTheme — fertig ist Ihr Theme.

Diese Seite behandelt das Definieren und Installieren eines Themes. Wie Views Tokens nutzen — Stilfunktionen und Recipes — steht unter [Styling und Themes](https://zenit.z.express/de/docs/guide/styling).

## Das Standard-Theme

Das `Cx` jedes Fensters startet mit `ui.theme.dark`. Eine App, die nie `setTheme` aufruft, ist dunkel — so wie Hello Button. Für ein anderes Theme setzen Sie es in der ersten Zeile Ihrer Mount-Funktion, bevor irgendein Node existiert:

`main.zig`

```zig
fn mountUI(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    // Cx starts on ui.theme.dark. Pick the theme before building any node,
    // so every component reads the right tokens when it mounts.
    cx.setTheme(&ui.theme.light);

    const root = try ui.boxStyled(cx, S.root, .{});
    // …
    return root;
}
```

Bei mehreren Fenstern hat jedes sein eigenes `Cx`, setzen Sie das Theme also in jeder Mount-Funktion. zenit folgt dem Erscheinungsbild des Systems noch **nicht**: Ein Hell/Dunkel-Wechsel des Systems löst nur ein Neuzeichnen aus, und es gibt keine öffentliche API, um das System-Erscheinungsbild zu lesen. Bieten Sie Ihren Nutzern stattdessen einen Umschalter an (siehe „Wechsel zur Laufzeit“).

## Aufbau von ThemeTokens

`src/ui/theme.zig (Auszug)`

```zig
pub const ThemeTokens = struct {
    name: []const u8 = "unnamed",
    scheme: ColorScheme = .light,      // .light / .dark — the one flag for "is this dark?"
    color: ColorTokens,                // 54 colors, no defaults: you must provide all of them
    space: SpaceScale = .{},           // _0 … _12  (0 … 48 px, 4 px grid)
    radius: RadiusScale = .{},         // none sm md lg xl full
    font_size: FontSizeScale = .{},    // xxs … xxxl (9 … 24)
    border_width: BorderWidthScale = .{},
    size: SizeTokens = .{},            // checkbox, switch, badge, dot, close button
    control: ControlScale = .{},       // xs / sm / md / lg metrics for every control
    duration: DurationTokens = .{},    // fast / normal / slow (seconds)
    shadow: ShadowTokens = .{},        // sm / md / lg
};
```

Alles außer `color` hat Standardwerte. Die 54 Farben in `ColorTokens` haben keine, schreiben Sie also kein Struct-Literal von Grund auf — kopieren Sie `ui.theme.light`, `ui.theme.dark` oder `ui.theme.high_contrast` und ändern Sie nur, was Sie brauchen.

> NOTE
> 
> **Hell und Dunkel unterscheiden Sie über scheme.** `scheme` ist das einzige Hell/Dunkel-Flag: Der Notifier wählt danach seine Palette, DevTools leitet daraus seine Farben ab. Suchen Sie nie in `name` nach „dark“ — das High-Contrast-Theme ist rein schwarz, und sein Name sagt nichts dergleichen.

## Ein Marken-Theme ableiten

Schreiben Sie Themes als `const`s auf Container-Ebene; der Initialisierungsblock läuft zur Compile-Zeit und das Ergebnis liegt im statischen Speicher, sodass der Zeiger, den `cx.setTheme(&brand)` behält, während der gesamten Programmlaufzeit gültig bleibt. Nehmen Sie nie die Adresse eines Themes, das in einer lokalen Variable einer Funktion liegt.

`theme.zig`

```zig
// theme.zig — your app's themes. Container-level consts: they are
// evaluated at compile time and live for the whole program, which is
// what cx.setTheme(&brand) needs (it keeps the pointer).
const ui = @import("ui");

pub const brand: ui.ThemeTokens = blk: {
    var t = ui.theme.light; // copy a complete preset (every color is required)
    t.name = "brand";
    t.scheme = .light;

    const blue = ui.Color.hex(0x1144AA);
    t.color.accent = blue;                       // links, checked states, active tabs…
    t.color.accent_hover = ui.Color.hex(0x0D3688);
    t.color.accent_subtle = ui.Color.hex(0xE6ECF7);
    t.color.button_primary_bg = blue;            // primary buttons read these two,
    t.color.button_primary_fg = ui.Color.WHITE;  //   not accent
    t.color.border_focus = blue;
    t.color.selection_bg = ui.Color.hex(0xC9D6F0);
    t.color.list_selection_bg = ui.Color.hex(0xE6ECF7);

    t.radius.lg = 10;
    t.control.md.radius = 10;
    t.control.md.padding_h = 14;
    break :blk t;
};
```

> WARNING
> 
> **Komponenten-Tokens folgen accent nicht.** In den Presets sind `accent` und `button_primary_bg` unabhängige Werte — nichts wird abgeleitet. Wenn Sie die Markenfarbe ändern, ändern Sie `accent`, `button_primary_bg` / `button_primary_fg` und `border_focus` gemeinsam, sonst behalten Primary-Buttons die Preset-Farbe.

## Ein passendes dunkles Theme

Leiten Sie die dunkle Variante von `ui.theme.dark` ab, statt das helle Marken-Theme umzufärben: Die Dutzenden Hintergrund-, Text- und Rahmenwerte im dunklen Preset sind als Satz abgestimmt. Auf dunklen Hintergründen muss die Markenfarbe meist aufgehellt werden, und der Text von Primary-Buttons wird dunkel.

`theme.zig`

```zig
pub const brand_dark: ui.ThemeTokens = blk: {
    var t = ui.theme.dark; // start from the dark preset, not from brand
    t.name = "brand_dark";
    t.scheme = .dark;      // Notification palettes and DevTools key off this

    const blue = ui.Color.hex(0x7FA2F0); // lighter on dark backgrounds
    t.color.accent = blue;
    t.color.button_primary_bg = blue;
    t.color.button_primary_fg = ui.Color.hex(0x0D1119);
    t.color.border_focus = blue;
    break :blk t;
};
```

![brand (hell)](https://zenit.z.express/media/theme-brand.webp?v=588f003545)brand (hell)

![brand_dark](https://zenit.z.express/media/theme-brand-dark.webp?v=854df7b29f)brand\_dark

![ui.theme.dark (Standard)](https://zenit.z.express/media/theme-default-dark.webp?v=3b5b953dc1)ui.theme.dark (Standard)

Derselbe Hello Button, nur das Argument von setTheme ist geändert — alle drei vom E2E-harness im echten Fenster aufgenommen.

## Welche Tokens was steuern

Gruppiert danach, was Komponenten tatsächlich lesen (Quellcode v0.1.0-alpha). Beginnen Sie beim Theming mit diesen:

| Token | Hauptsächlich genutzt von |
| --- | --- |
| `fg_primary · fg_secondary · fg_tertiary · fg_disabled` | Nahezu alle Texte und Icons |
| `bg_primary · bg_secondary · bg_tertiary` | Panels, Karten, Calendar, Chip, Progress- / Slider-Spuren, Skeleton |
| `bg_hover · bg_active` | Hover- und Gedrückt-Zustände klickbarer Controls |
| `accent` | Aktivierte Checkbox / Switch, Links, Tabs, Breadcrumb, ausgewählte Daten, Badge |
| `button_primary_bg · button_primary_fg` | Primary Button (hover / gedrückt aus dem Hintergrund abgeleitet); fg auch für invertierten Text auf Badge, Chip und Tabs |
| `border · border_strong · separator` | Rahmen und Trennlinien |
| `border_focus · input_bg · input_border · selection_bg` | Input, Select, NumberStepper, DatePicker und andere Felder |
| `success · warning · danger · info · status_fg` | Alert, Badge, Tag, Progress, danger Button |
| `list_hover_bg · list_selection_bg` | Menu, DropdownMenu, ComboBox, Table, Tree, DataTable |
| `checkbox_* · switch_thumb · switch_track_off` | Nicht aktivierte und deaktivierte Checkbox / Switch |
| `tooltip_bg · tooltip_fg · overlay · scrollbar_thumb` | Tooltip, Modal- / Sheet-Abdunklung, Scrollbars |

> WARNING
> 
> **Diese Tokens liest noch keine Komponente.** `accent_muted`, `bg_inset`, `button_secondary_bg`, `button_secondary_fg`, `switch_track_on` (ein eingeschalteter Switch nutzt `accent`), `table_header_bg`, `list_selection_hover_bg` sowie `success_subtle` / `warning_subtle` / `danger_subtle` / `info_subtle`. Sie zu ändern hat derzeit keinen sichtbaren Effekt; Ihre eigenen Stilfunktionen können sie trotzdem verwenden.

Einige Komponenten bringen eigene Farben mit, statt `ColorTokens` zu lesen: Der Notifier wählt anhand von `scheme` zwischen seinen eingebauten hellen und dunklen Paletten, und GlassBox-Tönungen sowie einige Details von Progress und Select sind feste Werte. Sie folgen Hell/Dunkel, aber nicht Ihrer Markenfarbe.

## Radien, Abstände und Control-Maße

Nicht-Farb-Tokens lassen sich Feld für Feld überschreiben. Am nützlichsten ist `control`: Button, Input, Select, ComboBox, DatePicker, DateRangePicker, Tabs und Chip teilen sich vier Maßsätze, xs / sm / md / lg. Die Höhe eines Controls ist kein Token — sie wird berechnet:

| Größe | Standardhöhe = padding\_y × 2 + font\_size × line\_height |
| --- | --- |
| `xs` | 2.5 × 2 + 12 × 1.25 = 20 |
| `sm` | 4.5 × 2 + 12 × 1.25 = 24 |
| `md` | 7.25 × 2 + 14 × 1.25 = 32 |
| `lg` | 10 × 2 + 16 × 1.25 = 40 |

Um md-Controls höher zu machen, ändern Sie also `t.control.md.padding_y` oder die Schriftgröße, und alle md-Controls folgen; codieren Sie keine `height` für ein Control fest. Halten Sie `icon_size` nicht größer als die Zeilenhöhe.

## Wechsel zur Laufzeit

`cx.setTheme` kann jederzeit aufgerufen werden: Es tauscht den Token-Zeiger aus, spielt die `on_theme`\- / `before_render`\-Hooks im gesamten `cx.root`\-Teilbaum (inklusive Overlay-Portal) erneut ab und fordert ein Neuzeichnen an.

`prefs.zig`

```zig
const Prefs = struct {
    cx: *ui.Cx,
    dark: bool = false,

    pub fn toggle(self: *Prefs) void {
        self.dark = !self.dark;
        self.cx.setTheme(if (self.dark) &themes.brand_dark else &themes.brand);
    }
};

// In mountUI:
const prefs = try cx.bindState(Prefs, .{ .cx = cx });
cx.setTheme(&themes.brand);
// …
.on_click = cx.on(Prefs, prefs, Prefs.toggle),
```

```zig
// A reactive read of the current theme (lives as long as `scope`).
const theme_sig = try cx.themeSignal(scope);
const is_dark = theme_sig.get().scheme == .dark;
```

> NOTE
> 
> **Was ein Wechsel zur Laufzeit erreicht.** Mit `boxStyled` / `textStyled` erzeugte Nodes und Komponenten mit Theme-Hooks färben sich sofort um; die meisten `ui.widgets.*` lesen Tokens beim Mounten, bauen Sie diese Teilbäume für einen vollständigen Wechsel also neu auf. Apps, die ihr Theme einmal beim Start wählen, sind nicht betroffen.

## Vor der Auslieferung

-   `scheme` passt zum Hintergrund (ein dunkler Hintergrund muss `.dark` sein).
    
-   `button_primary_fg` auf `button_primary_bg` und `status_fg` auf `danger` erreichen mindestens 4.5:1 — dieselbe Prüfung, mit der die eingebauten Presets getestet werden.
    
-   `border` bleibt von `bg_primary` unterscheidbar (die Preset-Tests verlangen ≥ 1.2).
    
-   Stellen Sie das Storybook auf Ihr Theme um und klicken Sie sich durch die gängigen Komponenten (die Story Component Wall ist ein guter Anfang).
