If you’ve written SwiftUI, Jetpack Compose, or Flutter, BESPA’s programming model will feel familiar. All four belong to the same family — declarative typed view-trees — where:
BESPA is the same shape, applied to the server. The renderer is your net/http handler instead of Apple’s graphics pipeline, and the diff travels over HTTP as HTML fragments instead of being applied locally — but the programming model is the same.
A greeting that updates as the user types — in SwiftUI:
// SwiftUI
struct GreetingView: View {
@State var name: String = ""
var body: some View {
VStack {
TextField("Your name", text: $name)
Text("Hello, \(name)!")
.font(.headline)
}
}
}
And in BESPA:
// BESPA
func handleHome(w http.ResponseWriter, r *http.Request) {
state := wf.StateOf(r)
wf.Page().Add(
wf.Form().Add(
wf.InputText("name", "").
WithPlaceholder("Your name").
WithAutoSubmit(true),
),
wf.HeadlineMedium("Hello, ", state.Get("name"), "!").
HideIfEmpty(r, "name").
RedrawIfChanged(r, "name"),
).Draw(w, r)
}
Different language, different runtime, same shape: a function that returns a tree of typed view nodes, where each node opts in to the state it cares about. The framework figures out the rest.
| SwiftUI / Compose / Flutter | BESPA |
|---|---|
struct ContentView: View / @Composable fun X() / Widget build() |
A Go handler that returns a widget tree |
var body: some View { ... } |
wf.Page().Add(...) |
Text("Hi").font(.title) |
wf.HeadlineMedium("Hi") |
VStack { ... }, HStack { ... }, Row { ... } |
wf.Toolbar().AddLeft(...), wf.Splitter(...).AddLeft(...) |
@State var x: String triggers rebuild |
URL state ?x=... triggers rebuild |
@Binding, @ObservedObject, @EnvironmentObject |
wf.StateOf(r).Get("x") everywhere |
| The framework diffs the view tree and repaints | The framework diffs which widgets opted in, swaps fragments by data-id |
| Hot reload via Xcode preview | Hot reload via go run . |
Strongly typed: refactor a .font() rename and the compiler catches it |
Strongly typed: refactor a WithColorPrimary() and the compiler catches it |
SwiftUI’s some View keeps .font(.title) returning the concrete view type, so the chain doesn’t lose its identity. BESPA does the same thing with Go generics: every widget embeds *WidgetBase[T] where T is the widget’s own pointer type, so the WithFoo methods on the base return *T, not *WidgetBase. The chain stays typed all the way to the end:
// SwiftUI — opaque return type, fluent chain stays on the concrete type
Text("Hi").font(.title).foregroundColor(.blue)
// BESPA — generic WidgetBase[T] returns the concrete pointer, so chains
// stay typed all the way through.
wf.HeadlineMedium("Hi").
WithColorPrimary().
RedrawIfChanged(r, "name")
The model is the same; the runtime isn’t. Things to recalibrate:
@State change. BESPA rebuilds on the server after a round-trip — a fetch posts the state delta, the server re-renders the affected widgets, and the client swaps fragments by data-id.@State, @Binding, @EnvironmentObject). BESPA: the URL query string. Less expressive, but persistent, bookmarkable, and trivially debuggable.@ViewBuilder lets you write VStack { Text("a"); Text("b") } without a separator. Go variadic functions get the same effect with .Add(a, b, c)..animation() and withAnimation { } have no direct analog — animations in BESPA are CSS transitions on the rendered HTML.go run .. The Go side is still fast (sub-second compiles for a typical app) — just not interactive.If you came from one of those frameworks, the following habits work on day one:
wf.StateOf(r).Get(...). Same idea as lifting @State to a parent view.body discipline.BESPA isn’t a new idea — it applies a well-established programming model to Go. Other frameworks in the same architectural family:
Adjacent but different — these solve the same end-goal (server-rendered HTML with partial updates) without the typed view-tree:
<turbo-frame> and <turbo-stream>. Template-first.Client-side cousins — same view-tree shape, but they render in the browser rather than the server:
The four-cell positioning: typed-view-tree × server-rendered. Blazor Server is the C# cell, Vaadin Flow the Java cell, Wt the C++ cell, and BESPA the Go cell. Of those four, the only one that emits HTML in pure handler-returns-tree form (no template language alongside) is BESPA.
Basics → Incremental updates — the diff/repaint protocol, the part of BESPA that doesn’t have a SwiftUI analog.
Extend → Widget anatomy — the WidgetBase[T] generic and how it gives chained methods their return type.
Get started — install and run the equivalent of a “hello, world” view function.