---
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://ui.surf/zenit/de/docs/guide/theming
language: de
alternate_en: https://ui.surf/zenit/docs/guide/theming.md
alternate_zh: https://ui.surf/zenit/zh/docs/guide/theming.md
alternate_es: https://ui.surf/zenit/es/docs/guide/theming.md
alternate_ja: https://ui.surf/zenit/ja/docs/guide/theming.md
alternate_ko: https://ui.surf/zenit/ko/docs/guide/theming.md
alternate_fr: https://ui.surf/zenit/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://ui.surf/zenit/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://ui.surf/zenit/media/theme-brand.webp?v=588f003545)brand (hell)

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

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

Derselbe Hello Button, nur das Argument von setTheme ist geändert. Alle drei wurden 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).
