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).