TinyWasm Components
A catalog of reusable, efficient, and WebAssembly-ready UI components for the TinyWasm ecosystem.
Features
- One way to build: a component is just
Render() (+ optional Init(ctx)). No
OnMount/OnUpdate/OnUnmount, no manual Update() — 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/dom for type-safe, declarative UI construction.
- Theme Integration: Consumes canonical tokens from
tinywasm/dom.
- Explicit Registration: Only pay for what you use (tree-shakeable).
Installation
go get github.com/tinywasm/components
To enable the default theme, inject dom.ThemeCSS into your page <head> once via your site builder.
Quick Start
1. Import Components
import (
"github.com/tinywasm/components/actionbutton"
"github.com/tinywasm/components/contentcard"
. "github.com/tinywasm/dom"
)
2. Instantiate and Render
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)
}
The Component API (build your own)
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/DeriveBool for 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.
Available Components
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.
Development
See Component Skill Guide to learn how to build your own components.
Prerequisites
- Go 1.25+
- TinyGo (for WASM compilation)
Running Tests
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