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.
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:
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
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.
Ein Marken-Theme ableiten
Schreiben Sie Themes als consts 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 — 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;
};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.
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)
brand_dark
ui.theme.dark (Standard)Welche Tokens was steuern
Gruppiert danach, was Komponenten tatsächlich lesen (Quellcode v0.1.0-alpha). Beginnen Sie beim Theming mit diesen:
fg_primary · fg_secondary · fg_tertiary · fg_disabledNahezu alle Texte und Iconsbg_primary · bg_secondary · bg_tertiaryPanels, Karten, Calendar, Chip, Progress- / Slider-Spuren, Skeletonbg_hover · bg_activeHover- und Gedrückt-Zustände klickbarer ControlsaccentAktivierte Checkbox / Switch, Links, Tabs, Breadcrumb, ausgewählte Daten, Badgebutton_primary_bg · button_primary_fgPrimary Button (hover / gedrückt aus dem Hintergrund abgeleitet); fg auch für invertierten Text auf Badge, Chip und Tabsborder · border_strong · separatorRahmen und Trennlinienborder_focus · input_bg · input_border · selection_bgInput, Select, NumberStepper, DatePicker und andere Feldersuccess · warning · danger · info · status_fgAlert, Badge, Tag, Progress, danger Buttonlist_hover_bg · list_selection_bgMenu, DropdownMenu, ComboBox, Table, Tree, DataTablecheckbox_* · switch_thumb · switch_track_offNicht aktivierte und deaktivierte Checkbox / Switchtooltip_bg · tooltip_fg · overlay · scrollbar_thumbTooltip, Modal- / Sheet-Abdunklung, ScrollbarsEinige 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:
xs2.5 × 2 + 12 × 1.25 = 20sm4.5 × 2 + 12 × 1.25 = 24md7.25 × 2 + 14 × 1.25 = 32lg10 × 2 + 16 × 1.25 = 40Um 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.
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),// 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;Vor der Auslieferung
- ✓
schemepasst zum Hintergrund (ein dunkler Hintergrund muss.darksein). - ✓
button_primary_fgaufbutton_primary_bgundstatus_fgaufdangererreichen mindestens 4.5:1 — dieselbe Prüfung, mit der die eingebauten Presets getestet werden. - ✓
borderbleibt vonbg_primaryunterscheidbar (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).