DOM Bindings
The bind: and on: directives allow you to link Go variables and browser events directly in the HTML, without writing any JavaScript code.
The bind:content Directive
One-way Go β DOM binding. Updates the innerHTML of an element whenever the Signal mutates:
<goscript client>
import "stew/state"
message := state.New("Hello!")
</goscript>
<h1 bind:content={{ message.Get() }}>Loading...</h1>
The compiler injects a unique id on the element, and the initial HTML content is automatically replaced and reacts to variable changes through Auto-Tracking (Signals).
The bind:value Directive
Two-way binding on <input> elements: Go β DOM and DOM β Go in real-time:
<goscript client>
import "stew/state"
name := "World" // If unbound by a signal
message := state.New("")
// Update on click or other event
updateMessage := func(val string) {
message.Set("Hello " + val + " !")
}
</goscript>
<input type="text" on:input={{ updateMessage(this.Get("value").String()) }} />
<p bind:content={{ message.Get() }}></p>
With the Signal system, you listen for the input event and update your internal Signal, which automatically triggers the DOM update.
The on:click Directive
Attaches a click handler directly in the HTML. The expression is executed upon clicking:
<button on:click={{ count++ }}>+</button>
Signal Detection Heuristic
The Stew compiler transparently detects the use of Signals. If it spots a .Get() call in an expression (e.g., count.Get()), it will automatically wrap the bound HTML block in a state.Effect(func() { ... }) system.
- Generates a unique identifier (
id="stew-bind-page-N") and links the targeted element using a selective asynchronous diff when the associated signal is modified.
β οΈ Limitation: Reactivity in HTML Attributes
The {{ if }} blocks or expressions inserted directly inside HTML attributes (e.g., class="... {{ if ... }}") are not reactive via Wasm.
If they were, creating the reactive container would severely break the syntactic validity of the HTML attribute. However, they will retain their behavior during the initial Server-side Rendering (SSR).
To graphically mutate a class on click, the official recommendation is to use the stew/ui package (for ToggleClass, AddClass, etc.), access this in the event handler, or encapsulate complex state logic within a separate .stew component.
Complex Expressions
Thanks to balanced brace support, you can write complex Go code directly in your bindings:
<!-- Inline anonymous function -->
<button on:click={{ func() {
console.Log("Clicked!")
nav.To("/home")
} }}>
Home
</button>
<!-- Multiple statements (separated by ;) -->
<button on:click={{ count++; console.Log("Incremented") }}>+</button>
Generic on:<event> Directive
Any DOM event can be targeted using the on:<eventName> syntax:
<input type="text" on:focus={{ inputFocused = true }} />
<div on:mouseover={{ hovered = true }} on:mouseleave={{ hovered = false }}></div>
Generates: wasm.OnEvent("id", "focus", func() { inputFocused = true })
Internal Mechanism
When the compiler encounters a bind: or on: attribute on an HTML element:
- It generates a unique identifier
"stew-bind-page-N". - It injects the
id="stew-bind-page-N"attribute on the preceding HTML element in the stream. - It removes the
bind:/on:attribute from the final HTML (invisible to browsers). - It generates the corresponding binding code in the
main()function of the Wasm bundle.
| Directive | Direction | Generates |
|---|---|---|
| bind:content={{ var.Get() }} | Signal β DOM | wasm.BindBlock(id, func(){...}) |
| on:click={{ expr }} | DOM β Go | wasm.OnEvent(id, "click", func(){expr}) |
| on:<event>={{ expr }} | DOM β Go | wasm.OnEvent(id, "event", func(){expr}) |