Theme¶
Rezi theming is semantic-only.
The public theme contract is ThemeDefinition. Applications pass a
ThemeDefinition to createApp(...), createNodeApp(...), and app.setTheme(...).
There is no separate public legacy Theme shape anymore.
Built-in presets¶
Rezi ships six built-in presets:
darkThemelightThemedimmedThemehighContrastThemenordThemedraculaTheme
import { darkTheme, nordTheme } from "@rezi-ui/core";
app.setTheme(darkTheme);
app.setTheme(nordTheme);
ThemeDefinition shape¶
ThemeDefinition contains:
namecolorsspacingfocusIndicatorwidget
colors holds the semantic app palette (bg.*, fg.*, accent.*, focus.*,
selected.*, disabled.*, diagnostic.*, border.*, plus success,
warning, error, and info).
widget holds advanced surface palettes:
widget.syntaxwidget.diffwidget.logswidget.toastwidget.chart
All color values are packed Rgb24 integers. Use rgb(...) or color(...) to
author them.
Creating a theme¶
createThemeDefinition(name, colors, options?) creates a complete frozen theme.
If spacing, focusIndicator, or widget are omitted, Rezi fills sensible
defaults.
import { createThemeDefinition, rgb } from "@rezi-ui/core";
export const brandTheme = createThemeDefinition(
"brand",
{
bg: {
base: rgb(10, 14, 20),
elevated: rgb(15, 20, 28),
overlay: rgb(24, 30, 40),
subtle: rgb(20, 25, 34),
},
fg: {
primary: rgb(231, 236, 242),
secondary: rgb(142, 155, 170),
muted: rgb(96, 107, 121),
inverse: rgb(10, 14, 20),
},
accent: {
primary: rgb(255, 180, 84),
secondary: rgb(89, 194, 255),
tertiary: rgb(149, 230, 203),
},
success: rgb(170, 217, 76),
warning: rgb(255, 180, 84),
error: rgb(240, 113, 120),
info: rgb(89, 194, 255),
focus: {
ring: rgb(255, 180, 84),
bg: rgb(26, 31, 38),
},
selected: {
bg: rgb(39, 55, 71),
fg: rgb(231, 236, 242),
},
disabled: {
fg: rgb(96, 107, 121),
bg: rgb(15, 20, 28),
},
diagnostic: {
error: rgb(240, 113, 120),
warning: rgb(255, 180, 84),
info: rgb(89, 194, 255),
hint: rgb(149, 230, 203),
},
border: {
subtle: rgb(26, 31, 38),
default: rgb(96, 107, 121),
strong: rgb(142, 155, 170),
},
},
);
Validation¶
Use validateTheme(theme) to enforce the hardened contract.
Validation checks:
- every required semantic color token
- every required widget palette token
spacing.xs,spacing.sm,spacing.md,spacing.lg,spacing.xl,spacing.2xlfocusIndicator.boldandfocusIndicator.underline- packed
Rgb24color values
Validation errors are path-specific. For example:
Theme validation failed at colors.accent.primary: expected packed Rgb24 integer ...Theme validation failed: missing required token path(s): widget.chart.primary, spacing.md
Extending and scoped overrides¶
Use extendTheme(base, overrides) to derive a new full theme definition.
import { darkTheme, extendTheme, rgb } from "@rezi-ui/core";
const brandDark = extendTheme(darkTheme, {
colors: {
accent: {
primary: rgb(255, 140, 90),
},
},
focusIndicator: {
bold: true,
underline: false,
},
});
Scoped subtree overrides use the same override shape:
ui.themed(
{
colors: { accent: { primary: rgb(255, 140, 90) } },
spacing: { md: 3 },
},
[ui.text("Only this subtree changes")],
);
Scoped overrides inherit unspecified values from the parent theme and can
override colors, spacing, focusIndicator, and widget palettes.
Runtime switching¶
app.setTheme(nextTheme) accepts a ThemeDefinition.
Behavior:
- allowed before
start()and while running - throws on re-entrant render/commit updates
- no-ops when the exact same theme object is passed again
- triggers a full redraw, with optional interpolation when
themeTransitionFrames > 0
Color helpers¶
import { resolveColorToken, tryResolveColorToken } from "@rezi-ui/core";
const fg = resolveColorToken(darkTheme, "fg.primary");
const accent = tryResolveColorToken(darkTheme, "accent.primary");
Related helpers:
resolveColorToken(theme, path)tryResolveColorToken(theme, path)resolveColorOrRgb(theme, colorOrPath, fallback)isValidColorPath(path)