Template Syntax
RTML is rfw’s declarative template language. It extends HTML with variables, commands (prefixed with @), and constructors (square-bracket annotations). Templates connect the DOM to Go state and events.
Building Blocks
| Construct | Syntax | Purpose |
|---|---|---|
| Variable | {{expr}} |
Insert reactive values |
| Command | @directive:arg |
Control flow, events, bindings |
| Constructor | [name] |
Annotate elements (refs, keys) |
<div [userCard]>
<h2>{{user.Name}}</h2>
<button @on:click:save>Save</button>
</div>
Text Interpolation
Use {{expr}} to insert reactive values:
<p>Count: {{Count.Get}}</p>
<p>Hello, {{Name.Get}}</p>
When the bound signal or store key changes, only the affected text node updates, no virtual DOM.
Calling Functions
Expressions can invoke component methods:
<time>{{FormatDate createdAt}}</time>
Keep expression functions pure and inexpensive.
Template Expressions with @expr
@expr: computes a value inline from an expression:
<p>Total: @expr:Count.Get * 2</p>
<span>@expr:Price.Get * Qty.Get</span>
@expr: is evaluated reactively, when any referenced signal changes, the output updates.
Signal Bindings
@signal:name binds a local reactive signal:
<p>@signal:Count</p>
<input value="@signal:Name:w">
<input type="checkbox" checked="@signal:Done:w">
<textarea>@signal:Bio:w</textarea>
Append :w on form controls for two-way binding (writes back to the signal on input). Without :w, the binding is read-only.
Tip: Inside
{{}}expressions you can reference signals by prop name:{{Count.Get}}. For text-only positions,@signal:Countis equivalent.
Store Bindings
@store:Module.Store.Key reads from a global store:
<p>Shared: @store:app.default.count</p>
<input value="@store:app.default.count:w">
Append :w on form controls for two-way binding. Other components reading the same store key update automatically.
Event Bindings
@on:event:handler binds a DOM event to a handler:
<button @on:click:Increment>+</button>
<form @on:submit.prevent:Save>...</form>
The handler name must match a method on the struct or a name registered via comp.On().
Modifiers
| Modifier | Effect |
|---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.once |
Removes listener after first call |
Chain modifiers after the event name:
<form @on:submit.prevent.stop:Save>
<button @on:click.once:Launch>Launch once</button>
Conditionals
@if, @else-if, @else, @endif:
@if:Count.Get > 0
<p>Positive</p>
@else-if:Count.Get == 0
<p>Zero</p>
@else
<p>Negative</p>
@endif
Branches may contain any RTML content, including includes, loops, and nested conditionals.
List Rendering
@for:alias in collection … @endfor:
@for:item in items
<li>{{item.Text}}</li>
@endfor
Range
@for:i in 0..N.Get
<span>{{i}}</span>
@endfor
Maps
@for:key, val in obj
<p>{{key}}: {{val}}</p>
@endfor
Keyed Items
Use [key {{expr}}] for stable identity and efficient reorders:
@for:todo in todos
<li [key {{todo.ID}}]>{{todo.Text}}</li>
@endfor
Without keys, elements may be recreated when order changes.
Includes and Slots
Include
@include:component renders a child component:
@include:content
With props:
@include:Card:{title: "Hello"}
Slot
@slot:name … @endslot defines a named outlet:
<!-- Layout.rtml -->
<root>
<nav>My App</nav>
<main>@slot:content
<p>Default content</p>
@endslot</main>
</root>
Parents fill slots from their own template:
@include:Layout
<div slot="content">Custom content here</div>
@end
Constructors
Square-bracket annotations in start tags:
Template Refs
[name] marks an element for lookup via GetRef("name"):
<div [list]></div>
func (c *MyComp) OnMount() {
el := c.GetRef("list")
el.SetHTML("updated")
}
List Keys
[key {{expr}}] gives loop items stable identity (see List Rendering).
Attribute Bindings
Attributes accept {{expr}} values:
<img src="{{avatar}}" alt="{{name}}">
<div class="btn-{{variant}}"></div>
Boolean Attributes
When an expression evaluates to a boolean, the attribute is present only when truthy:
<button disabled="{{isDisabled}}">Save</button>
@expr Directive
For computed values inline:
<p>Doubled: @expr:Count.Get * 2</p>
<span>@expr:Price.Get * Qty.Get</span>
Any referenced signals trigger re-evaluation. Equivalent to a computed property but defined directly in the template.
Ternary Expressions (Natural Syntax)
Use if ... then ... else ... inside @expr: for inline conditionals:
<p>@expr:Count.Get > 0 then "Positive" else "Zero or negative"</p>
<span>@expr:Active.Get then "Yes" else "No"</span>
The condition before then follows the same expression rules as @if: — comparisons, logical operators, and signal field access all work:
<p>@expr:Count.Get > 10 then "high" else Count.Get > 5 then "medium" else "low"</p>
Legacy ? : syntax is also supported:
<p>@expr:Count.Get > 0 ? "Positive" : "Non-positive"</p>
Prefer the if ... then ... else form — it reads naturally in RTML templates.
Dynamic Components
Switch components at runtime with rt-is:
<div rt-is="{{current}}"></div>
The placeholder is replaced with the component whose name matches current.
Security
Interpolated content is escaped by default to prevent XSS. Only render trusted data; use components and slots for rich markup.
Quick Reference
| Syntax | Purpose |
|---|---|
{{expr}} |
Text interpolation |
@signal:Name |
Read a signal |
@signal:Name:w |
Two-way signal binding |
@store:M.S.K |
Read a store key |
@store:M.S.K:w |
Two-way store binding |
@on:event:Handler |
Bind event |
@on:event.prevent.stop:Handler |
Bind event with modifiers |
@if:cond … @endif |
Conditional |
@else-if:cond |
_else-if branch |
@else |
else branch |
@for:x in items … @endfor |
List iteration |
@include:Name |
Include component |
@include:Name:{key: val} |
Include with props |
@slot:name … @endslot |
Define slot |
@expr:expression |
Template expression |
@expr:cond then X else Y |
Ternary in expressions |
@expr:cond ? X : Y |
Legacy ternary syntax |
[refName] |
Template ref |
[key {{expr}}] |
List key |
rt-is="{{name}"} |
Dynamic component |