{}

Widget anatomy

Every widget in BESPA is a Go struct that implements the Widget interface. In practice you never implement it from scratch — you embed WidgetBase[T] and write a Draw method.

The interface

The contract is six methods, but only one of them (Draw) is something you’re likely to write:

// widget/widget.go
type Widget interface {
    ID() string
    SetID(id string)
    Children() []Widget
    Draw(w io.Writer, r *http.Request) error
    Drawn(r *http.Request) bool
    Shown(r *http.Request) bool
}

Everything else has a sensible default in WidgetBase. ID/SetID plumb the partial-redraw protocol; Children defaults to nil (no children); Drawn and Shown are controlled by RedrawIfChanged / HideIf — see Extend → State-aware widgets.

The base type

The minimal widget — a Go struct, a constructor that wires up WidgetBase, optional builder methods, and a Draw:

type GreetingWidget struct {
    *widget.WidgetBase[*GreetingWidget]
    name string
}

func (f MyFactory) Greeting(name string) *GreetingWidget {
    g := &GreetingWidget{name: name}
    g.WidgetBase = widget.NewWidgetBase(g)
    return g
}

func (g *GreetingWidget) WithName(name string) *GreetingWidget {
    g.name = name
    return g
}

func (g *GreetingWidget) Draw(w io.Writer, r *http.Request) error {
    return widget.NewWriterAssistant(w).
        WriteString("<span data-id=\"", g.ID(), "\">Hello, ", html.EscapeString(g.name), "!</span>").
        Err()
}

Three things going on:

Composing instead of escaping by hand

In practice almost no widget writes raw HTML. Use Tag — the framework’s tag builder that handles escaping, attribute merging, and conditional rendering:

func (g *GreetingWidget) Draw(w io.Writer, r *http.Request) error {
    return factory.Tag("span").
        Class("Greeting").
        Attr("data-id", g.ID()).
        Add("Hello, ", g.name, "!").
        When(g.Shown(r)).
        Draw(w, r)
}

This is the form you’ll see used by every widget under basic/ and form/. Tag("span").Add(g.name, ...) calls the framework’s Any coercion under the hood, which escapes plain strings; numbers, times, and other types convert to widgets too.

Where the widget lives

By convention each widget gets its own .go file plus optional .css and .js siblings — e.g. basic/heading.go + basic/heading.css. The factory file in the package’s root registers all the assets in one init(). See Extend → Assets & CSS for that pattern.

See also

Composing existing widgets — for most cases you don’t need to write a Draw at all.

State-aware widgets — how Drawn / Shown / RedrawIfChanged interact with your Draw.

Read the source of basic/heading.go for a tiny worked example, or basic/menu.go for one that uses companion CSS and JS.