astwerk

Attribute bindings

The WASM twin of the compiled-markup attribute bindings: four one-property bindings — href, class, disabled, style — each driven by its own signal. Change a signal and exactly that property updates, nothing else re-renders. The compiled-markup version is Attribute bindings.

templ.guide

a toggled class

styled text

How it works

State

go
url := reactive.NewSignal("https://templ.guide")
active := reactive.NewSignal(true)
busy := reactive.NewSignal(false)
colour := reactive.NewSignal("#4f7cff")

One signal per property the demo touches: the link target, the class, the disabled flag and the colour.

The elements

go
reactive.BindAttr(root.Find("[data-bind-href]"), "href", url.Get)
reactive.BindClass(root.Find("[data-bind-class]"), "on", active.Get)
reactive.BindAttr(root.Find("[data-bind-disabled]"), "disabled", func() string {
	if busy.Get() {
		return "disabled"
	}
	return "" // empty removes the attribute
})
reactive.BindStyle(root.Find("[data-bind-style]"), "color", colour.Get)

Each binding owns one property of one element. BindAttr sets a string attribute and removes it when the value is empty; BindClass toggles one class; BindStyle sets one CSS property.

The buttons

go
reactive.On(root.Find("[data-toggle-class]"), "click", func(wasmwrap.Event) {
	active.Update(func(v bool) bool { return !v })
})
reactive.On(root.Find("[data-toggle-disabled]"), "click", func(wasmwrap.Event) {
	busy.Update(func(v bool) bool { return !v })
})
reactive.On(root.Find("[data-recolour]"), "click", func(wasmwrap.Event) {
	colour.Set("#ff4f7c")
})
reactive.On(root.Find("[data-retarget]"), "click", func(wasmwrap.Event) {
	url.Set("https://pkg.go.dev/github.com/LukasDerBaum42/astwerk")
})

Each button writes its signal — Update toggles a bool, Set stores a constant. Because each binding owns its property, changing one signal updates exactly that element's property — nothing else re-renders.

Full source
go
url := reactive.NewSignal("https://templ.guide")
active := reactive.NewSignal(true)
busy := reactive.NewSignal(false)
colour := reactive.NewSignal("#4f7cff")

reactive.Hydrate("#bindings-demo", func(root wasmwrap.Element) {
	reactive.BindAttr(root.Find("[data-bind-href]"), "href", url.Get)
	reactive.BindClass(root.Find("[data-bind-class]"), "on", active.Get)
	reactive.BindAttr(root.Find("[data-bind-disabled]"), "disabled", func() string {
		if busy.Get() {
			return "disabled"
		}
		return ""
	})
	reactive.BindStyle(root.Find("[data-bind-style]"), "color", colour.Get)

	reactive.On(root.Find("[data-toggle-class]"), "click", func(wasmwrap.Event) {
		active.Update(func(v bool) bool { return !v })
	})
	reactive.On(root.Find("[data-toggle-disabled]"), "click", func(wasmwrap.Event) {
		busy.Update(func(v bool) bool { return !v })
	})
	reactive.On(root.Find("[data-recolour]"), "click", func(wasmwrap.Event) {
		colour.Set("#ff4f7c")
	})
	reactive.On(root.Find("[data-retarget]"), "click", func(wasmwrap.Event) {
		url.Set("https://pkg.go.dev/github.com/LukasDerBaum42/astwerk")
	})
})
How this demo runs

scripts/demo.go is compiled to demo.wasm by CompileFrom and loaded by WasmScript — see Scripts & WebAssembly. Without WebAssembly the page still renders; only the bindings stay put.