flowchart LR
Start([Request arrives]) --> Auth{Authenticated?}
Auth -- No --> Login[Redirect to login]
Auth -- Yes --> Handler[Run handler]
Handler --> Render[Render widgets]
Render --> Diff[Diff fragments]
Diff --> Reply([Send response])sequenceDiagram
participant Browser
participant Server
participant DB
Browser->>Server: GET /page?x=1
Server->>DB: Query
DB-->>Server: Rows
Server-->>Browser: HTML
Browser->>Server: POST ?x=2 (Bespa-Fetch: 1)
Server-->>Browser: Partial fragments
Note over Browser: Swap by data-idstateDiagram-v2
[*] --> Idle
Idle --> Loading: submit
Loading --> Success: 2xx
Loading --> Error: 4xx/5xx
Success --> Idle: reset
Error --> Idle: retrygantt
title Release plan
dateFormat YYYY-MM-DD
section Design
Mockups :done, des1, 2026-05-01, 7d
Review :active, des2, 2026-05-08, 3d
section Build
Widgets : b1, after des2, 10d
Docs : b2, after b1, 5dclassDiagram
class Widget {
+ID() string
+Draw(w, r) error
}
class WidgetBase~T~ {
-shown bool
+RedrawIfChanged(r, keys) T
+HideIf(pred) T
}
class ChartWidget
class MermaidWidget
Widget <|.. WidgetBase
WidgetBase <|-- ChartWidget
WidgetBase <|-- MermaidWidgetflowchart TB
A[Request] --> B[Router]
B --> C{Method}
C -->|GET| D[Handler GET]
C -->|POST| E[Handler POST]
D --> F[Read state]
E --> F
F --> G[Build tree]
G --> H[Draw]
H --> I[Compress]
I --> J[Response]
J --> K[Client]
K --> L{Bespa-Fetch?}
L -->|Yes| M[Swap fragments]
L -->|No| N[Replace page]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)
}
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)
}
http://localhost:8080.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"),
),
)