Skip to content

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 of setTheme. Without it, instantiating a patched template throws inline <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.