{}

Declarative views

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.

Side-by-side

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.

The mental-model map

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

The generic-self-typing trick

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")

Where it diverges

The model is the same; the runtime isn’t. Things to recalibrate:

What transfers immediately

If you came from one of those frameworks, the following habits work on day one:

Related frameworks

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:

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.

See also

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.