Interactivity

The server drives the UI

You write Go. The browser gets HTML. There's no client framework to ship. Almost everything you'll do is one of five moves:

islandssignalspush (SSE)formsoptimistic actions

Islands

A click changes one part of the page. The click calls the server, the server returns new HTML for that part, and the runtime swaps it in. Use it for sort, paginate, expand, add a row — anything that isn't a whole new page.

customers.go
7 lines
// Sort headers fire an RPC instead of navigating. The handler// returns the new table HTML; the runtime swaps this island in place.ui.DataTable(ui.DataTableConfig{    Rows:           rows,    IslandSignal:   "customers",    IslandEndpoint: "/customers/table",})
/docs/interactive-patterns →

Signals

Typed client state you declare once. Many parts of the page read the same value; change it in one place and every reader updates. Use it for a cart count, a selected row, a filter shared across widgets.

signals.go
5 lines
// One declaration — namespaced, typed, seeded into the client store.company := store.New("app").String("company", "Acme Corp")// Any number of readers; change it once and every binding updates.company.Bind(ctx, "span", map[string]string{"id": "co-name"})
/docs/signal-store →

Push (SSE)

For background events, not user clicks. The server sends an update over a Server-Sent Events stream and connected pages react. Use it for a live who's-here roster, a job that finished, a number that changed somewhere else.

presence.go
10 lines
// On a background change, re-render and push to each open session.host.Islands.SetOnPresenceChange(func(topic string) {    roster := renderRoster(host.Islands.PresenceRoster(topic))    for _, sid := range host.Islands.PresenceSessions(topic) {        host.Islands.PushUpdate(island.IslandUpdate{            IslandID: "roster-" + topic,            HTML:     string(roster),        }, sid)    }})
/docs/runtime-contract →

Forms

The server validates. On error it returns the form with the messages already in place, swapped like any island — no full reload, and no client-side validation to keep in sync with the server's.

login.go
5 lines
// errs is the server's validation result (ui.FieldErrors). On error// the form comes back with each message in place, swapped like an island.ui.Form(ui.FormConfig{Method: "POST", Action: "/login", SubmitLabel: "Sign in", Errors: errs},    ui.FormFieldFor(errs, "email", ui.FormFieldConfig{Label: "Email", For: "email", Input: emailInput}),)
/docs/form-module →

Optimistic actions

The button updates before the server answers. The runtime fires the request, shows the success label right away, and rolls back if the server returns an error. Use it for approve, archive, like — actions that almost always succeed.

actions.go
6 lines
ui.OptimisticAction(ui.OptimisticActionConfig{    Endpoint:     "/posts/42/archive",    IdleLabel:    "Archive",    SuccessLabel: "Archived",    Variant:      ui.ButtonPrimary,})
/docs/interactive-patterns →