Gothic Framework G symbol

Client Side State

Client Side State is Gothic Framework's way to write reactive state and DOM logic in pure Go, with no JavaScript bundle, no React, and no Alpine.

ClientSideState is a function you declare on any RouteConfig. Inside it you write reactive browser-side logic in Go that compiles to WebAssembly and runs in the browser.

If you know React, think of it as a component's local state, but written in Go. You declare values with CreateObservable, react to changes with Observe, and expose handlers to HTML with CreateWasmFunc.

Important: You must dot-import pkg/wasm (import . "github.com/felipegenef/gothicframework/v2/pkg/wasm") for use these helpers, otherwise you will get a tinygo compile error.

Important: Keep HTMX for server-driven flows and ClientSideState for the small, reactive bits in between. Together they cover almost every interaction without shipping a JavaScript framework.

Note: ClientSideState declared on a component is scoped to that component only. Sibling components or multiple copies of the same component each get their own isolated state and functions, so they never interfere with each other. If you need a function shared across all components on a page, declare it in the page-level ClientSideState instead. For shared reactive values, use Topics.

Here is an example. The counter declares state, updates the DOM, and exposes a function to HTML:

import  ( 
	 "strconv" 
	 routes   "github.com/felipegenef/gothicframework/v2/pkg/helpers/routes" 
	 .   "github.com/felipegenef/gothicframework/v2/pkg/wasm" 
) 

 type   CounterProps  =  interface {} 

 var   CounterConfig  =  routes. RouteConfig [ CounterProps ]{ 
	 Type:         routes. STATIC, 
	 HttpMethod:   routes. GET, 
	 // Runs once in the browser when the component mounts. 
	 ClientSideState:   func () { 
		 count  :=  CreateObservable ( 0 ) 

		 // Re-runs whenever count.Set(...) is called. 
		 Observe ( func () { 
			 SetText ( "display" ,  strconv. Itoa ( count. Get ())) 
		 },  count ) 

		 // Exposes window.increment() to plain HTML onclick attributes. 
		 CreateWasmFunc ( "increment" ,  func () { 
			 count. Set ( count. Get () +  1 ) 
		 }) 
	 }, 
 } 

On the HTML side you just need the matching id and an onclick that calls the registered function:

<!-- inside your .templ component --> 
 <span id= "display" >0</span> 
 <button onclick= "increment()" >+1</button> 

Reach for ClientSideState when you need local state or DOM updates that HTMX cannot model on its own. Counters, live form validation, multi-selects, and synced inputs are all great fits.

Curious how observables actually track dependencies under the hood? Let's look at Observe, Peek, and batching next!