Styling
Theme
Rezi supports two related theme shapes:
Rezi supports two related theme shapes:
ThemeDefinition: semantic tokens (bg.base,fg.primary,accent.primary, etc.)Theme: runtime flat palette used by the renderer
app.setTheme(...) accepts either shape.
Built-in presets
Rezi ships six semantic presets:
darkThemelightThemedimmedThemehighContrastThemenordThemedraculaTheme
import { darkTheme, nordTheme } from "@rezi-ui/core";
app.setTheme(darkTheme);
app.setTheme(nordTheme);Validation
Use validateTheme(theme) to enforce required theme structure before use:
import { validateTheme } from "@rezi-ui/core";
validateTheme(myTheme);Validation checks:
- All required semantic color tokens exist
- Every color token is valid RGB (
r/g/binteger in0..255) - Required spacing entries exist:
xs,sm,md,lg,xl,2xl - Focus indicator style tokens are present and valid
Error messages are path-specific, for example:
Theme validation failed at colors.accent.primary.r: ...Theme validation failed: missing required token path(s): colors.error, spacing.md
Extension / inheritance
Use extendTheme(base, overrides) to derive variants without cloning full objects:
import { darkTheme, extendTheme } from "@rezi-ui/core";
const brandDark = extendTheme(darkTheme, {
colors: {
accent: {
primary: { r: 255, g: 180, b: 84 },
},
},
});Guarantees:
- deep merge (override wins, other tokens inherited)
- returns a new theme object
- does not mutate
base - validates merged output
Contrast utility and WCAG checks
Use contrastRatio(fg, bg) for WCAG 2.1 contrast calculations:
import { contrastRatio } from "@rezi-ui/core";
const ratio = contrastRatio({ r: 0, g: 0, b: 0 }, { r: 255, g: 255, b: 255 }); // 21Built-in preset verification in tests:
- all six presets pass WCAG AA (
>= 4.5:1) for primaryfg/bg highContrastThemepasses WCAG AAA (>= 7:1) for primaryfg/bg
Runtime switching guarantees
app.setTheme(nextTheme) behavior:
- allowed before
start()and while running - throws on re-entrant render/commit calls (
ZRUI_UPDATE_DURING_RENDER,ZRUI_REENTRANT_CALL) - no-op when effective theme identity is unchanged
- theme changes trigger a full redraw (incremental reuse is bypassed on theme ref change)
Component-level scoped overrides
box, row, and column support a scoped theme prop:
import { ui } from "@rezi-ui/core";
ui.column({}, [
ui.text("parent"),
ui.box({ theme: { colors: { primary: { r: 80, g: 200, b: 120 } } } }, [
ui.text("scoped"),
]),
ui.text("parent again"),
]);Rules:
- scope applies to container subtree
- nested overrides compose (inner scope wins)
- leaving a scoped container restores parent theme
- partial overrides inherit unspecified parent tokens
Color token helpers
import { darkTheme, resolveColorToken, tryResolveColorToken } from "@rezi-ui/core";
const fg = resolveColorToken(darkTheme, "fg.primary");
const result = tryResolveColorToken(darkTheme, "accent.primary");Related helpers:
resolveColorToken(theme, path)tryResolveColorToken(theme, path)resolveColorOrRgb(theme, colorOrPath, fallback)isValidColorPath(path)