{}

Composing existing widgets

Most useful widgets aren’t new HTML — they’re a recognizable combination of existing widgets you want to drop in by name. A search box is a form with one input. A toast is a snackbar with an icon. A status pill is a chip with a colored dot. These don’t need Tag or hand-written HTML; they just delegate to a sub-tree built from the framework’s existing widgets.

Delegating to a sub-tree

A SearchBox that wraps a Form + InputText + WithAutoSubmit is twelve lines:

// factory is the package-local composite factory — see the next section.
type SearchBoxWidget struct {
    *widget.WidgetBase[*SearchBoxWidget]
    name        string
    placeholder string
}

func (f MyFactory) SearchBox(name string) *SearchBoxWidget {
    s := &SearchBoxWidget{name: name, placeholder: "Search…"}
    s.WidgetBase = widget.NewWidgetBase(s)
    return s
}

func (s *SearchBoxWidget) WithPlaceholder(p string) *SearchBoxWidget {
    s.placeholder = p
    return s
}

func (s *SearchBoxWidget) Draw(w io.Writer, r *http.Request) error {
    return factory.Form().Add(
        factory.InputText(s.name, "").
            WithPlaceholder(s.placeholder).
            WithAutoSubmit(true),
    ).Draw(w, r)
}

The Draw method builds the sub-tree on the fly and delegates to its Draw. From the consumer’s side it looks like any other widget: wf.SearchBox("q").WithPlaceholder("Find people").

Implementing Children too

The version above works for static rendering, but the framework’s traversal pass also walks Children() to collect input values, assign stable IDs, and propagate redraw. For a widget that exposes form inputs to the outside world, return the same sub-tree from Children():

func (s *SearchBoxWidget) Children() []widget.Widget {
    return []widget.Widget{ s.composed() }
}

func (s *SearchBoxWidget) Draw(w io.Writer, r *http.Request) error {
    return s.composed().Draw(w, r)
}

// composed builds the sub-tree once so Children and Draw agree.
func (s *SearchBoxWidget) composed() widget.Widget {
    return factory.Form().Add(
        factory.InputText(s.name, "").
            WithPlaceholder(s.placeholder).
            WithAutoSubmit(true),
    )
}

Factor the sub-tree out into a helper so Draw and Children return the same shape — otherwise a state walk and a render walk may disagree on which widgets exist, and stable ID generation falls apart.

Getting at the framework's widgets

Inside your widget you’ll want to construct Form, InputText, etc. Build a package-local factory struct that composes whichever families you need:

// At the top of your package:
import "github.com/microbus-io/bespa/basic"
import "github.com/microbus-io/bespa/form"

var factory = struct {
    widget.WidgetFactory
    basic.BasicFactory
    form.FormFactory
}{}

This is the same pattern every widget package in the framework uses — basic/factory.go composes widget.WidgetFactory + itself, form/factory.go adds basic on top, and so on.

When to compose vs. write Draw

If everything you need is in the framework already, compose. If you need a specific HTML element (a <canvas>, a SVG, a third-party JS init script), drop down to Tag — see Extend → Widget anatomy. The two approaches mix freely; a complex widget often uses Tag for one outer element and composed widgets for everything inside.

See also

Widget anatomy — for the lower-level case where you do write Draw.

Read table/quicksearch.go in the framework source — it’s a real composed widget that wraps InputText with table-aware defaults.