Variants and inline themes
variant=
Every widget draws in one variant of its theme entry. The built-in names are primary (the default), secondary, accent, neutral, danger, success, warning and info. There are also 56 numbered slots, custom1 and up; XML accepts custom1 to custom40 by number.
xml
<Panel layout="hbox" spacing="8">
<Button text="Save" />
<Button text="Cancel" variant="secondary" />
<Button text="Delete" variant="danger" />
</Panel>Badge and Tag also take kind="...", which sets the same thing.
Your own variant names
A theme can declare named variants, for example card, muted or heading. Each name is given one of the custom slots, and theme entries style it like any built-in variant.
json
{
"variants": ["card", "heading", "muted"],
"colors": { "fg": "#D4D4D4", "fgMuted": "#9E9E9E", "surface1": "#1A1C22", "border": "#2E3340" },
"components": {
"label": {
"heading": { "textColor": { "default": "$fg" } },
"muted": { "textColor": { "default": "$fgMuted" } }
},
"panel": {
"card": {
"backgroundColor": { "default": "$surface1" },
"borderColor": { "default": "$border" },
"cornerRadius": 8
}
}
}
}Give the compiled theme to the registry so the names resolve, then set it on the instantiated root so the widgets draw with it:
ts
import { Component, DEFAULT_STAGE, Screen, TemplateRegistry, Theme, loadTheme } from "willowui";
const THEME_JSON: string = `{
"variants": ["card", "muted"],
"colors": { "fgMuted": "#9E9E9E", "surface1": "#1A1C22" },
"components": {
"label": { "muted": { "textColor": { "default": "$fgMuted" } } },
"panel": { "card": { "backgroundColor": { "default": "$surface1" }, "cornerRadius": 8 } }
}
}`;
const TEMPLATE: string = `
<Panel variant="card" layout="vbox" spacing="6" padding="12">
<Label text="Profile" />
<Label variant="muted" text="Last seen 3 minutes ago" />
</Panel>`;
const screen: Screen = new Screen();
const theme: Theme = loadTheme(THEME_JSON);
const reg: TemplateRegistry = new TemplateRegistry();
reg.setTheme(theme);
reg.registerXML("card", TEMPLATE);
const root: Component = reg.instantiate("card", null, screen);
root.setTheme(theme);
screen.add(root);
DEFAULT_STAGE.setScene(engine.root2d);
DEFAULT_STAGE.add(screen);
App.onFrame((dt: float): void => { DEFAULT_STAGE.update(dt); });An unknown variant name falls back to primary. Variant names are not case-sensitive.
Inline theme patches
A template can carry its own theme tweaks in a <Theme> element. Its text is a JSON merge patch (RFC 7396) applied to the registry's base theme: keys you give replace or extend the base, and a key set to null removes it.
xml
<Panel layout="vbox" spacing="8" padding="12">
<Theme>
{ "components": { "button": { "primary": { "backgroundColor": { "default": "#2a6a3a" } } } } }
</Theme>
<Label text="This screen has green buttons." />
<Button text="Continue" />
</Panel>Rules:
- The registry needs the base theme as JSON text: call
reg.setThemeJSON(json)instead ofsetTheme. Without it, instantiating a patched template throwsinline <Theme> patches require SetThemeJSON on the registry. - Put
<Theme>directly inside the root element. Only the root's patch is applied. - The
<Theme>text must be valid JSON, or the template does not compile. - The merged theme is compiled when you instantiate and set on the returned root. Other templates from the same registry keep the base theme.
Theme files
Theme files can also be compiled with thicket tool themec <theme-dir> -o <file>.theme and loaded with loadThemeAny(path) from willowui, which accepts a compiled .theme, a theme folder, or a single .json and caches the compile. The Theming guide covers the theme file, nine-slice images and themec.