Template & binding
Dynamic attributes, {#if}/{#if:destroy}, {#each}, bind:.
A .thr component's template is always valid XML — every node id is
automatically cached (no hand-written findNode), and every
{expr} attribute becomes a real, generated reactive push.
Static and dynamic attributes
<Rectangle id="background" color="0x1A1A1AFF" width="{width}" height="{height}">
<Label id="label" text="{favoritesLabel}" color="{textColor}" />
</Rectangle>
A static attribute value containing </>/&
can be written with the raw character (text="a < b") — the compiler escapes it
correctly when generating the underlying XML. Don't pre-escape it by hand
(text="a < b"): the compiler would escape that text too, so the literal
entity code ends up rendered on screen instead of the character you meant.
{#if} — toggle mode
Reactively binds visible on a synthetic wrapper Group.
The subtree is always constructed and stays registered (including for focus purposes) — cheap toggling, no
teardown.
{#if favoriteCount == 0}
<Label id="emptyBadge" text="Add your first!" color="{textColor}" />
{/if}{#if:destroy} — construct/destroy mode
Hand-constructs the subtree at runtime when the condition first becomes true, and tears it down (with focus correctly handed back, per the vacuum rule) when it goes false again — a genuinely clean slate on every remount, not a cheap visibility flip.
{#if:destroy showCelebration}
<Label id="celebration" text="Nice streak!" color="{textColor}" />
{/if}{#each} — keyed list rendering
{#each <collection> as <item> (<key>)} does a real keyed add/remove/reposition
diff, preserving node identity across reorders. Nests with {#if}/{#if:destroy}
in both directions, including loop-in-loop. The collection may be a plain array or a SceneGraph node (iterated
over its own children).
{#if:destroy hasLoaded}
{#each schedule as day (day.id)}
<Label id="row" text="{prefix + day.title}" translation="{[rowX, day.y]}"
focusable="true" on:key[OK]="{selectDay(day)}" />
{#if:destroy day.isToday}
<Label id="todayBadge" text="(today)" translation="{[todayBadgeX, day.y]}" />
{/if}
{/each}
{/if}bind:
bind:<childField>={<stateName>} pulls a value out of a
child node's field into a state, reactively, whenever the child's field
changes. Despite the "two-way binding" name in early planning docs, this is
one-directional only: child → state. It never pushes a value into the
child — pushing, when wanted, is just an ordinary attr="{expr}" dynamic
attribute on a separate attribute.
<TextEditBox id="searchBox" bind:text="{searchText}" focusable="true" />
<Label id="searchEcho" text="{searchText}" />Reference implementation — apps/template-and-binding-demo
Every mechanism on this page has a router-mounted, scaled
chapter in apps/template-and-binding-demo — 4 chapters
(/attributes through /bind),
reachable with REWIND/FAST-FORWARD once compiled and sideloaded. Each chapter shows a default
example alongside a deliberately different, customized one — e.g. /each's
plain 5-row list next to a "Reverse order"/"Rotate order" pair that reorders the SAME backing
items, proving each row's own color travels with it (node identity preserved across the keyed
diff, not destroyed and rebuilt), and /if-toggle-vs-destroy's
two side-by-side counters showing that a {#if} panel's
internal state survives a hide/show cycle while a {#if:destroy}
panel's resets to zero. See findings/template-and-binding-demo-app.md
for what each chapter covers and findings/demo-app-conventions.md
for the app-structure convention it follows.
⚠️ Not (yet) supported
- ○
bind:inside an{#each}body — rejected at compile time. - ○ No built-in loop index variable inside
{#each}— a list item that needs its own layout position must carry that position as part of its own data (see the real-world workaround in ScheduleList.thr). - ○ A
{#each}block's items are not reachable by an enclosingLayoutGroup's automatic child layout — the wrapper Group sits between them. - ○ Every
{#each}item binding re-runs unconditionally on every reconcile — no fine-grained per-field diff.
Exact grammar: GRAMMAR.md. Full feature status: docs/features.md.