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

BESPA

Build single-page apps with just backend Go.

Declarative typed widgets in the shape of SwiftUI, Jetpack Compose, or Flutter — except the renderer is your net/http server. BESPA composes a tree of Go structs, ships HTML to the browser, and quietly swaps in updated fragments as state changes. No JavaScript framework. No build step. No bundler. Material Design 3 is the default.

func(w http.ResponseWriter, r *http.Request) {
	wf.Page().Add("Hello, World!").Draw(w, r)
}

Why BESPA?

Server-side rendering

Every byte of HTML is produced by your Go code. The first request returns a complete, indexable, accessible page — not a JS bootstrap shell.

Incremental redraws

A click on `?x=foo` posts the state back, the server re-renders only the affected widgets, and a ~5 KB client swaps the changed fragments into the DOM. No page flash, no full reload.

Strongly typed widgets

Every widget is a Go struct with chained builder methods. Refactor with confidence; the compiler catches what would be a runtime stack trace in JS.

Material Design 3

Color tokens, typography scale, elevation, and components ship in the box. Light and dark themes work without you writing a line of CSS.

Responsive design

Layouts adapt to the viewport out of the box — decks reflow, navigation collapses, and touch targets stay comfortable. The same app works on phones, tablets, and desktops without a separate mobile build.

Tiny client runtime

The total JavaScript needed to run a BESPA app is in the kilobytes, and it ships with the framework. You don't write or maintain any of it.

Drop-in library

BESPA is a Go package. Embed it in any `net/http` mux, in a Microbus.io service, or behind whatever HTTP middleware you already use. No new infrastructure.

Fast

Server-rendered HTML reaches the browser in milliseconds — no JS bundle to parse, no client-side hydration. State changes ship as minimal HTML patches and Go's `net/http` does the rest at native speed.

Extensible

Need a widget that doesn't exist yet? Define a Go struct, write a `Draw` method, register its CSS and JS with the asset registry, and it composes alongside everything that ships in the box — including your own published libraries.

ARIA compliant

Real semantic HTML, proper ARIA roles on modals, tabs, snackbars, and nav, keyboard focus management, and screen-reader-friendly labels on icons. Your app gets accessibility for free from the framework.

Hello, world!

The smallest BESPA app — 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)
}
That's it. No package.json, no webpack config, no separate frontend repo. `go run .` and you have a working interactive app at http://localhost:8080.

State drives the page

BESPA's only state-management primitive is the URL's query string. Links and form submissions update state; widgets that depend on a state variable declare it with RedrawIfChanged and re-render whenever it moves. A counter looks like this:
state := wf.StateOf(r)
count, _ := strconv.Atoi(state.Get("count"))

page := 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"),
	),
)
Click + or − and only the heading and the buttons re-render. See incremental updates for a walkthrough of the underlying mechanism.

See it in action

Each demo below opens in a modal frame — itself another BESPA widget. Or jump to the full showcase.

Forms

Every input widget — text, dates, ranges, dropdowns, ratings, the rich-text editor — on one page.
web_assetopen_in_new

Validation

Client-side and server-side validation working together with inline error messages.
web_assetopen_in_new

Data table

Sorting, filtering, paging, all driven by the same incremental-redraw plumbing.
web_assetopen_in_new

CRUD

Create / read / update / delete flow over a per-session in-memory directory.
web_assetopen_in_new

Charts

Apache ECharts wrapped as bespa widgets, themed against Material design tokens.
web_assetopen_in_new

Mermaid

Mermaid diagrams themed against Material tokens, with optional zoom and pan.
web_assetopen_in_new

Code blocks

Server-side syntax highlighting via Chroma, retheming live with the rest of the page.
web_assetopen_in_new

Learn more

Basics

How state changes flow from the browser to the server and back as targeted DOM swaps.

Build

Practical techniques for using BESPA as a library — when to redraw, forms, tables, modals.

Extend

Adding your own widgets to the framework and packaging them as reusable libraries.

Showcase

Every widget the framework ships with, with code-ready examples.