Theme
<theme-template>/<theme>, bare theme.a.b access, switchTheme.
A validated, runtime-switchable theme system: one canonical <theme-template>
declares the full shape and defaults, and any number of <theme name="...">
files partially override it.
Declaring the template and variants
A leaf is <name>: <Type> = <literal> (same shape as
field, minus the keyword); a group is <name>: { ... },
nestable arbitrarily. Each of these lives in its own file — the file's own name is a free choice, the compiler
finds them structurally by their root tag, not by filename.
<theme-template default="dark">
colors: {
background: string = "0x1A1A1AFF"
highlight: string = "0x0057FFFF"
text: string = "0xFFFFFFFF"
}
</theme-template>A variant may omit any member (it falls back to the template's default at that path), but anything it does provide must exist in the template at that path, be the same kind (group vs. leaf), and repeat the same type explicitly:
<theme name="light">
colors: {
background: string = "0xF5F5F5FF"
text: string = "0x111111FF"
}
</theme>Reading theme.a.b
Bare dot access, validated against the whole app's declared theme shape at compile time (an unknown member is a
compile error) — reads are fully reactive, so a runtime theme switch is reflected live everywhere it's read,
not just on next mount. Theme is read-only from components; there is no theme-write syntax.
<script>
derived backgroundColor: string = theme.colors.background
derived textColor: string = theme.colors.text
</script>
<component>
<Rectangle id="background" color="{backgroundColor}">
<Label id="label" color="{textColor}" />
</Rectangle>
</component>Switching at runtime
The initial active theme resolves at compile time: default="name" if present,
otherwise the first-declared variant, otherwise the template's own literal defaults. A generated
switchTheme(name) changes it at runtime — an unknown variant name is a
harmless no-op with a debug print, never a crash.
m.global.ft_theme.callFunc("switchTheme", "light")Reference implementation — apps/theme-demo
Every mechanism on this page has a router-mounted, scaled
chapter in apps/theme-demo — 3 chapters
(/theme-template,
/theme-access,
/switch-theme), reachable with REWIND/FAST-FORWARD once
compiled and sideloaded. Chapter 1 declares a small template (two groups,
colors/spacing) plus a
default variant that overrides every leaf and a second variant that overrides only two —
labeled readouts show exactly which leaves fall back to the template's own defaults when the
partial variant is active. Chapter 2 reads several theme.a.b
paths at once, both through a derived and directly inline in
a template binding, side by side, to show both access styles pull from the same live snapshot.
Chapter 3 calls the generated switchTheme(name) between the
two variants from chapter 1 with the whole screen visibly re-theming, then calls it again with an
unknown name to show the documented no-op in action. See
findings/theme-demo-app.md for what each chapter covers.
⚠️ Not (yet) supported
- ○ At most one
<theme-template>per app. - ○ No manifest-file or runtime-decided initial-theme selection — only the compile-time
default="name"fallback chain exists. - ○ Indexing through a theme leaf (e.g. treating a leaf value as if it were a group) — compile error.
Exact grammar: GRAMMAR.md. Full feature status: docs/features.md.