A catalog of reusable, efficient, and WebAssembly-ready UI components for the TinyWasm ecosystem.
- One way to build: a component is just
Render()(+ optionalInit(ctx)). NoOnMount/OnUpdate/OnUnmount, no manualUpdate()— impossible to wire wrong. - Fine-grained reactivity: dynamic state lives in typed signals; changing one patches only the bound DOM node — no whole-element re-render, no Virtual DOM.
- No generics: concrete typed signals (
SignalString/SignalBool/SignalNodes), matching the ecosystem's "cero any, cero map" rule — readable, TinyGo-friendly. - SSR/CSR Split: CSS and heavy assets are server-side only; WASM binaries remain tiny.
- Fluent API: Uses
tinywasm/domfor type-safe, declarative UI construction. - Theme Integration: Consumes canonical tokens from
tinywasm/dom. - Explicit Registration: Only pay for what you use (tree-shakeable).
go get github.com/tinywasm/componentsTo enable the default theme, inject dom.ThemeCSS into your page <head> once via your site builder.
import (
"github.com/tinywasm/components/actionbutton"
"github.com/tinywasm/components/contentcard"
. "github.com/tinywasm/dom"
)func main() {
// Create a button
btn := &actionbutton.ActionButton{
Text: "Click Me",
Variant: "primary",
OnClick: func(e Event) {
println("Button clicked!")
},
}
// Render to the DOM
Append("app", btn)
}A component implements only Render() *Element, plus an optional Init(ctx Ctx) that runs once
before the first render. Dynamic state lives in a signal; you change it with Set, and the bound
DOM updates itself — you never call Update().
Start here — the simplest reactive component. A signal, bound to text, changed on click:
import (
. "github.com/tinywasm/dom"
. "github.com/tinywasm/html"
)
type LikeButton struct {
Element // value-embed, never *Element
label *SignalString // dynamic state is a signal, not a plain field
}
func (c *LikeButton) Init(_ Ctx) { c.label = NewString("Like") } // starting state
func (c *LikeButton) Render() *Element {
return Button().
BindText(c.label). // show the signal
On("click", func(Event) { c.label.Set("Liked ♥") }) // change it → the button updates itself
}Three ideas: state is a signal, BindText shows it, Set changes it. That's the whole loop.
Growing up — computed text + show/hide. When a value is computed from state, pass a function to
BindTextFunc (no dependency list — it auto-detects the signals you read). Toggle() flips a bool:
type Disclosure struct {
Element
open *SignalBool
}
func (c *Disclosure) Init(_ Ctx) { c.open = NewBool(false) }
func (c *Disclosure) Render() *Element {
return Div(
Button().
BindTextFunc(func() string { if c.open.Get() { return "Hide" }; return "Show" }).
On("click", func(Event) { c.open.Toggle() }),
Show(c.open, func() *Element { return Div("details…") }), // mounts/unmounts reactively
)
}Signals (no generics — concrete typed cells):
| Type | Create | For |
|---|---|---|
*SignalString |
NewString("") |
text, attribute values, two-way input |
*SignalBool |
NewBool(false) |
class/attr toggles, Show conditions (Toggle() to flip) |
*SignalNodes |
NewNodes(...) |
keyed lists of rendered rows |
Bindings (each patches only its own DOM spot):
- Show a signal directly:
BindText,BindAttr,BindClass,BindAttrBool,Bind(two-way input, cursor/IME safe),BindChildren(keyed list),Autofocus,Show(cond, render). - Show a computed value (function, auto-tracked — no deps list):
BindTextFunc,BindAttrFunc,BindClassFunc,BindAttrBoolFunc. (DeriveString/DeriveBoolfor a named shared computed.)
For async/teardown, set signals from a goroutine in Init and use ctx.OnCleanup(fn).
See Component Skill Guide for the full standard.
See Component Catalog for full documentation.
- ActionButton: Primary/secondary actions with variants.
- ContentCard: Content container with header/body/footer.
- DataTable: Data tables with headers and rows.
- NavBar: Navigation menu with icon support.
- Dialog: Modal dialog overlays.
- ThemeToggle: Theme switcher (light/dark/auto).
Forms are NOT part of tinywasm/components. Use github.com/tinywasm/form directly — it is the standard form library for the tinywasm ecosystem.
See Component Skill Guide to learn how to build your own components.
- Go 1.25+
- TinyGo (for WASM compilation)
Use gotest (the tinywasm runner — WASM tests run against a real DOM), never go test:
go install github.com/tinywasm/devflow/cmd/gotest@latest
gotest