Guide / 12 β€” Isomorphic Wasm

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:

  1. It generates a unique identifier "stew-bind-page-N".
  2. It injects the id="stew-bind-page-N" attribute on the preceding HTML element in the stream.
  3. It removes the bind:/on: attribute from the final HTML (invisible to browsers).
  4. It generates the corresponding binding code in the main() function of the Wasm bundle.
Directive Direction Generates
bind:content={{ var.Get() }}Signal β†’ DOMwasm.BindBlock(id, func(){...})
on:click={{ expr }}DOM β†’ Gowasm.OnEvent(id, "click", func(){expr})
on:<event>={{ expr }}DOM β†’ Gowasm.OnEvent(id, "event", func(){expr})