{"modal":"/try/hello"}

Get started

BESPA is a Go library — no toolchain, no bundler, no separate frontend repo. Add one go get to your project, write a handler that returns a tree of widgets, and serve it. This page walks through the smallest end-to-end example.

1. Install

Add the module to your project:

go get github.com/microbus-io/bespa

2. Hello, world!

Put this in main.go. It’s a complete program — a name input and a greeting that updates as you type:

package main

import (
    "net/http"

    "github.com/microbus-io/bespa"
    "github.com/microbus-io/bespa/widget"
)

var wf = bespa.DefaultFactory{}

func handleHome(w http.ResponseWriter, r *http.Request) {
    state := wf.StateOf(r)
    page := wf.Page().Add(
        wf.AppBar("Hello"),
        wf.Form().Add(
            wf.InputText("name", "").
                WithPlaceholder("Your name").
                WithAutoSubmit(true),
        ),
        wf.HeadlineMedium("Hello, ", state.Get("name"), "!").
            HideIfEmpty(r, "name").
            RedrawIfChanged(r, "name"),
    )
    page.Draw(w, r)
}

func main() {
    http.HandleFunc("/bespa/", widget.AssetRegistry.ServeHTTP)
    http.HandleFunc("/", handleHome)
    http.ListenAndServe(":8080", nil)
}

Then run it and open the page:

go run .

Visit http://localhost:8080. Type your name. Notice the greeting updates without a full page reload, the URL changes to ?name=…, and the cursor stays in the input. That’s the entire BESPA loop: state in the URL, server re-renders affected widgets, the client swaps fragments in place.

Two lines in main matter:

3. State drives the page

A second handler shows the core idiom: every link is a state change, every widget that depends on the state opts in to redraw, and nothing else moves. A counter:

func handleCounter(w http.ResponseWriter, r *http.Request) {
    state := wf.StateOf(r)
    count, _ := strconv.Atoi(state.Get("count"))
    wf.Page().Add(
        wf.AppBar("Counter"),
        wf.HeadlineLarge(count).
            RedrawIfChanged(r, "count"),
        wf.Toolbar().AddLeft(
            wf.ButtonFilled("").Add("-").
                WithHref("?count=" + strconv.Itoa(count-1)).
                RedrawIfChanged(r, "count"),
            wf.ButtonFilled("").Add("+").
                WithHref("?count=" + strconv.Itoa(count+1)).
                RedrawIfChanged(r, "count"),
        ),
    ).Draw(w, r)
}

Click + or and only the heading and the two buttons (whose hrefs change) re-render. The toolbar, AppBar, and the rest of the page stay put. See Basics → Incremental updates for the underlying protocol.

4. Add more widgets

bespa.DefaultFactory gives you everything in basic/, form/, table/, and nav/ — the standard universe. Heavier widgets are opt-in packages: chart (Apache ECharts), code (Chroma syntax highlighting), richedit (Quill 2). Compose whichever you need into a single factory:

import (
    "github.com/microbus-io/bespa"
    "github.com/microbus-io/bespa/chart"
    "github.com/microbus-io/bespa/code"
)

// Compose the default widgets with optional packages and your own.
var wf = struct {
    bespa.DefaultFactory
    chart.ChartFactory
    code.CodeFactory
    // myorg.MyOrgFactory   // your own widget library
}{}

Go’s embedded-struct method resolution does the rest — wf.Chart(...) and wf.CodeBlock(...) show up alongside wf.Page(...) with no conflict. See Extend → Packaging to write your own.

Where to go next

Basics

How the framework works internally — state, incremental updates, nesting pages, frames.

Build apps

Practical recipes — handlers and routing, forms, tables, modals, navigation, theming.

Extend

Writing your own widgets and packaging widget libraries.

Showcase

Every widget the framework ships with, live and copyable.