dom

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 9, 2026 License: MIT Imports: 1 Imported by: 0

README

tinywasm/dom

Project Badges

Ultra-minimal DOM & event toolkit for Go (TinyGo WASM-optimized).

tinywasm/dom provides a minimalist, WASM-optimized way to interact with the browser DOM in Go, avoiding the overhead of the standard library and syscall/js exposure. It is designed specifically for TinyGo applications where binary size and performance are critical.

🚀 Features

  • Elm-Inspired Architecture: Component-local state with explicit updates (Model → Update → View)
  • Fluent Builder API: Chainable methods for concise, readable code
  • Hybrid Rendering: Choose DSL for dynamic components or string HTML for static ones
  • TinyGo Optimized: Avoids heavy standard library packages to keep WASM binaries <500KB
  • Direct DOM Manipulation: No Virtual DOM overhead. You control the updates.
  • ID-Based Caching: Efficient element lookup and caching strategy
  • Memory Safe: Automatic event listener cleanup on Unmount
  • Lifecycle Hooks: OnMount, OnUpdate, OnUnmount for fine-grained control

📦 Installation

go get github.com/tinywasm/dom

⚡ Quick Start

Example 1: Dynamic Component (Elm Pattern)

Counter component with state management:

//go:build wasm

package main

import (
	"github.com/tinywasm/dom"
	"github.com/tinywasm/fmt"
)

// Model (state)
type Counter struct {
	dom.BaseComponent
	count int
}

// View (rendering)
func (c *Counter) Render() dom.Node {
	return dom.Div().
		ID(c.GetID()).
		Class("counter").
		Append(
			dom.Button().
				Text("-").
				OnClick(c.Decrement),
		).
		Append(
			dom.Span().
				Class("count").
				Text(fmt.Sprint(c.count)),
		).
		Append(
			dom.Button().
				Text("+").
				OnClick(c.Increment),
		).
		ToNode()
}

// Update (state mutations)
func (c *Counter) Increment(e dom.Event) {
	c.count++
	c.Update() // Explicit re-render
}

func (c *Counter) Decrement(e dom.Event) {
	c.count--
	c.Update()
}

// Lifecycle (optional)
func (c *Counter) OnMount() {
	fmt.Println("Counter mounted with ID:", c.GetID())
}

func main() {
	counter := &Counter{count: 0}
	dom.Render("app", counter)
	select {}
}
Example 2: Static Component (String HTML)

For simple, static content use string HTML (smaller binary):

type Header struct {
	dom.BaseComponent
}

func (h *Header) RenderHTML() string {
	return `<header class="app-header">
		<h1>My Application</h1>
	</header>`
}

func main() {
	header := &Header{}
	dom.Render("app", header)
}

🎨 Fluent Builder API

The new fluent API allows chaining for concise, readable code:

dom.Div().
	ID("container").
	Class("flex items-center").
	Append(
		dom.Button().
			Text("Click me").
			OnClick(handleClick),
	).
	Append(
		dom.Span().
			Text("Hello World"),
	).
	Render("app") // Terminal operation

Available builders: Div(), Span(), Button(), H1(), H2(), H3(), P(), Ul(), Li(), Input(), Form(), A(), Img()

🔄 Lifecycle Hooks

Components can implement optional lifecycle interfaces:

type MyComponent struct {
	dom.BaseComponent
	data []string
}

// Called after component is mounted to DOM
func (c *MyComponent) OnMount() {
	c.data = fetchData()
	c.Update()
}

// Called after re-render (dom.Update)
func (c *MyComponent) OnUpdate() {
	fmt.Println("Component updated")
}

// Called before component is removed
func (c *MyComponent) OnUnmount() {
	// Cleanup resources
}

📝 Component Interface

All components must implement:

type Component interface {
	GetID() string
	SetID(string)
	RenderHTML() string  // OR Render() dom.Node
	Children() []Component
}

Two rendering options:

  1. RenderHTML() string - For static components (smaller binary)
  2. Render() dom.Node - For dynamic components (type-safe, composable)

Components can implement either or both. DOM checks Render() first, falls back to RenderHTML().

🎯 Hybrid Rendering Strategy

Choose the right rendering method for each component:

Component Type Method Benefit
Static (no interactivity) RenderHTML() string Smaller binary, less overhead
Dynamic (interactive, state) Render() dom.Node Type-safe, composable, fluent API
// Static: Use string HTML
type Footer struct {
	dom.BaseComponent
}
func (f *Footer) RenderHTML() string {
	return `<footer>© 2026 My App</footer>`
}

// Dynamic: Use DSL Builder
type TodoList struct {
	dom.BaseComponent
	todos []string
}
func (t *TodoList) Render() dom.Node {
	list := dom.Ul().ID(t.GetID())
	for _, todo := range t.todos {
		list.Append(dom.Li().Text(todo))
	}
	return list.ToNode()
}

🧩 Nested Components

Components can contain child components:

type MyList struct {
	dom.BaseComponent
	items []dom.Component
}

func (c *MyList) Children() []dom.Component {
	return c.items
}

func (c *MyList) Render() dom.Node {
	list := dom.Div().ID(c.GetID())
	for _, item := range c.items {
		list.Append(item) // Components can be children
	}
	return list.ToNode()
}

When you call dom.Render("app", myList), the library will:

  1. Render the HTML
  2. Call OnMount() for MyList
  3. Recursively call OnMount() for all items

The same recursion applies to Unmount(), ensuring all event listeners are cleaned up.

🎯 Event Handling

func (c *MyComponent) OnMount() {
	root, _ := dom.Get(c.GetID())

	// Event delegation using TargetID
	root.On("click", func(e dom.Event) {
		id := e.TargetID() // "item-1", "item-2", etc.
		// Handle logic...
	})
}

🔧 Core API

Package Functions
// Rendering
dom.Render(parentID, component)  // Replace parent's content
dom.Append(parentID, component)  // Append after last child
dom.Hydrate(parentID, component) // Attach to server-rendered HTML
dom.Update(component)            // Re-render in place

// Lifecycle
dom.Unmount(component)           // Remove and cleanup

// Element Access
dom.Get(id)                      // Get element by ID
dom.QueryAll(selector)           // Query by CSS selector

// Routing (hash-based)
dom.OnHashChange(handler)        // Listen to hash changes
dom.GetHash()                    // Get current hash
dom.SetHash(hash)                // Set hash
BaseComponent Helpers
type Counter struct {
	dom.BaseComponent
	count int
}

// Chainable helpers
counter.Update()              // Trigger re-render
counter.Unmount()             // Remove from DOM
counter.GetID()               // Get unique ID
counter.SetID("my-id")        // Set custom ID

📚 Documentation

For more detailed information, please refer to the documentation in the docs/ directory:

  1. Specification & Philosophy: Design goals, architecture, and key decisions.
  2. API Reference: Detailed definition of DOM, Element, and Component interfaces.
  3. Creating Components: Guide to building basic and nested components.
  4. Event Handling: Using the Event interface for clicks, inputs, and forms.
  5. Advanced Patterns: Dynamic lists, decoupling, and performance tips.
  6. Comparison: TinyDOM vs. syscall/js, VDOM, and JS frameworks.

🆕 What's New in v0.2.0

  • Elm-inspired architecture - Component-local state with explicit updates
  • Fluent Builder API - Chainable methods (dom.Div().ID("x").Class("y"))
  • Hybrid rendering - Choose DSL or string HTML per component
  • Lifecycle hooks - OnMount, OnUpdate, OnUnmount
  • Auto-ID generation - All components get unique IDs automatically
  • Smaller binaries - TinyGo-optimized, <500KB for typical apps

📊 Performance

Binary Size (TinyGo WASM):

  • Simple counter app: ~35KB (compressed)
  • Todo list with 10 components: ~120KB (compressed)
  • Full application: <500KB (compressed)

Compared to standard library approach: 60-80% smaller binaries.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append added in v0.2.0

func Append(parentID string, component Component) error

Append injects a component AFTER the last child of the parent element.

func GetHash added in v0.0.11

func GetHash() string

GetHash gets the current hash.

func Hydrate added in v0.2.0

func Hydrate(parentID string, component Component) error

Hydrate attaches event listeners to existing HTML.

func Log added in v0.0.7

func Log(v ...any)

Log provides logging functionality.

func Mount added in v0.0.7

func Mount(parentID string, component Component) error

Mount is an alias for Render for backward compatibility. Deprecated: use Render instead.

func OnHashChange added in v0.0.11

func OnHashChange(handler func(hash string))

OnHashChange registers a hash change listener.

func Render added in v0.2.0

func Render(parentID string, component Component) error

Render injects a component into a parent element.

func SetHash added in v0.0.11

func SetHash(hash string)

SetHash sets the current hash.

func SetLog added in v0.0.7

func SetLog(log func(v ...any))

SetLog sets the logging function.

func Unmount added in v0.0.7

func Unmount(component Component)

Unmount removes a component from the DOM.

func Update added in v0.2.0

func Update(component Component) error

Update re-renders a component.

Types

type BaseComponent added in v0.0.13

type BaseComponent struct {
	// contains filtered or unexported fields
}

BaseComponent is a helper struct that implements the Identifiable interface. Users can embed this in their components to automatically handle ID management.

func (*BaseComponent) Children added in v0.1.0

func (c *BaseComponent) Children() []Component

Children returns the component's children (nil by default).

func (*BaseComponent) GetID added in v0.2.0

func (c *BaseComponent) GetID() string

GetID returns the component's unique identifier.

func (*BaseComponent) Mount added in v0.2.0

func (c *BaseComponent) Mount(parentID string) *BaseComponent

Mount injects the component into a parent element.

func (*BaseComponent) Render added in v0.2.0

func (c *BaseComponent) Render() *BaseComponent

Render re-renders the component in place. This is used for chaining, e.g., component.SetState(...).Render()

func (*BaseComponent) RenderHTML added in v0.2.0

func (c *BaseComponent) RenderHTML() string

RenderHTML returns an empty string by default, satisfying the HTMLRenderer interface.

func (*BaseComponent) SetID added in v0.0.13

func (c *BaseComponent) SetID(id string)

SetID sets the component's unique identifier.

func (*BaseComponent) Unmount added in v0.2.0

func (c *BaseComponent) Unmount()

Unmount removes the component from the DOM.

func (*BaseComponent) Update added in v0.2.0

func (c *BaseComponent) Update() error

Update triggers a re-render of the component.

type Builder added in v0.2.0

type Builder struct {
	// contains filtered or unexported fields
}

Builder represents a DOM element in the fluent builder API.

func A added in v0.2.0

func A() *Builder

func Button added in v0.2.0

func Button() *Builder

func Div added in v0.2.0

func Div() *Builder

Factory functions

func Form added in v0.2.0

func Form() *Builder

func H1 added in v0.2.0

func H1() *Builder

func H2 added in v0.2.0

func H2() *Builder

func H3 added in v0.2.0

func H3() *Builder

func Img added in v0.2.0

func Img() *Builder

func Input added in v0.2.0

func Input() *Builder

func Li added in v0.2.0

func Li() *Builder

func P added in v0.2.0

func P() *Builder

func Span added in v0.2.0

func Span() *Builder

func Ul added in v0.2.0

func Ul() *Builder

func (*Builder) Append added in v0.2.0

func (b *Builder) Append(child any) *Builder

Append adds a child to the element.

func (*Builder) Attr added in v0.2.0

func (b *Builder) Attr(key, val string) *Builder

Attr sets an attribute on the element.

func (*Builder) Children added in v0.2.0

func (b *Builder) Children() []Component

Children returns the component's children (components only).

func (*Builder) Class added in v0.2.0

func (b *Builder) Class(class string) *Builder

Class adds a class to the element.

func (*Builder) GetID added in v0.2.0

func (b *Builder) GetID() string

GetID returns the element's ID.

func (*Builder) ID added in v0.2.0

func (b *Builder) ID(id string) *Builder

ID sets the ID of the element.

func (*Builder) Mount added in v0.2.0

func (b *Builder) Mount(parentID string) error

Mount is an alias for Render.

func (*Builder) OnChange added in v0.2.0

func (b *Builder) OnChange(handler func(Event)) *Builder

OnChange adds a change event handler.

func (*Builder) OnClick added in v0.2.0

func (b *Builder) OnClick(handler func(Event)) *Builder

OnClick adds a click event handler.

func (*Builder) OnInput added in v0.2.0

func (b *Builder) OnInput(handler func(Event)) *Builder

OnInput adds an input event handler.

func (*Builder) Render added in v0.2.0

func (b *Builder) Render(parentID string) error

Render renders the element to the parent. This is a terminal operation.

func (*Builder) RenderHTML added in v0.2.0

func (b *Builder) RenderHTML() string

RenderHTML renders the element to HTML string.

func (*Builder) SetID added in v0.2.0

func (b *Builder) SetID(id string)

SetID sets the element's ID.

func (*Builder) Text added in v0.2.0

func (b *Builder) Text(text string) *Builder

Text adds a text node child.

func (*Builder) ToNode added in v0.2.0

func (b *Builder) ToNode() Node

ToNode converts the element to a Node tree.

type CSSProvider added in v0.0.13

type CSSProvider interface {
	RenderCSS() string
}

CSSProvider is an optional interface for components that need to inject CSS.

type ChildProvider added in v0.1.0

type ChildProvider interface {
	// Children returns the child components.
	Children() []Component
}

ChildProvider returns the child components of a component.

type Component

type Component interface {
	Identifiable
	HTMLRenderer
	ChildProvider
}

Component is the minimal interface for components. All components must implement this for both SSR (backend) and WASM (frontend).

type DOM

type DOM interface {
	// Get retrieves an element by its ID.
	// It uses an internal cache to avoid repeated DOM lookups.
	// Returns the element and a boolean indicating if it was found.
	Get(id string) (Element, bool)

	// Render injects a component into a parent element.
	// 1. It calls component.Render() (if ViewRenderer) or component.RenderHTML()
	// 2. It sets the content of the parent element (found by parentID)
	// 3. It calls component.OnMount() to bind events
	Render(parentID string, component Component) error

	// Append injects a component AFTER the last child of the parent element.
	// Useful for dynamic lists.
	Append(parentID string, component Component) error

	// Hydrate attaches event listeners to existing HTML without re-rendering it.
	Hydrate(parentID string, component Component) error

	// OnHashChange registers a listener for URL hash changes.
	OnHashChange(handler func(hash string))

	// GetHash returns the current URL hash (e.g., "#help").
	GetHash() string

	// SetHash updates the URL hash.
	SetHash(hash string)

	// QueryAll finds all elements matching a CSS selector.
	QueryAll(selector string) []Element

	// Unmount removes a component from the DOM (by clearing the parent's HTML or removing the node)
	// and cleans up any event listeners registered via the Element interface.
	Unmount(component Component)

	// Update re-renders the component in its current position in the DOM.
	Update(component Component) error

	// Log provides logging functionality using the log function passed to New.
	Log(v ...any)
}

DOM is the main entry point for interacting with the browser. It is designed to be injected into your components.

type Element

type Element interface {

	// SetText sets the text content of the element.
	// Accepts variadic arguments that are concatenated without spaces.
	//
	// Examples:
	//   elem.SetText("Count: ", 42)              // -> "Count: 42"
	//   elem.SetText(D.Hello, " ", D.User)       // -> "Hello User" (localized)
	SetText(v ...any)

	// SetHTML sets the inner HTML of the element.
	// Accepts variadic arguments that are concatenated without spaces.
	// Supports format strings with % specifiers.
	//
	// Examples:
	//   elem.SetHTML("<div>", "content", "</div>")  // -> "<div>content</div>"
	//   elem.SetHTML("<h1>%v</h1>", 42)             // -> "<h1>42</h1>"
	//   elem.SetHTML("<span>%L</span>", D.Hello)    // -> "<span>Hello</span>" (localized)
	SetHTML(v ...any)

	// AppendHTML adds HTML to the end of the element's content.
	// Useful for adding items to a list without re-rendering the whole list.
	// Accepts variadic arguments that are concatenated without spaces.
	//
	// Examples:
	//   elem.AppendHTML("<li>", item, "</li>")
	//   elem.AppendHTML("<div class='%s'>%v</div>", "item", count)
	AppendHTML(v ...any)

	// Remove removes the element from the DOM.
	Remove()

	// AddClass adds a CSS class to the element.
	AddClass(class string)

	// RemoveClass removes a CSS class from the element.
	RemoveClass(class string)

	// ToggleClass toggles a CSS class.
	ToggleClass(class string)

	// SetAttr sets an attribute value.
	// Accepts variadic arguments that are concatenated without spaces.
	//
	// Examples:
	//   elem.SetAttr("id", "item-", 42)           // -> id="item-42"
	//   elem.SetAttr("href", "/page/", pageNum)   // -> href="/page/5"
	//   elem.SetAttr("title", D.Hello)            // -> title="Hello" (localized)
	SetAttr(key string, v ...any)

	// GetAttr retrieves an attribute value.
	GetAttr(key string) string

	// RemoveAttr removes an attribute.
	RemoveAttr(key string)

	// Value returns the current value of an input/textarea/select.
	Value() string

	// SetValue sets the value of an input/textarea/select.
	// Accepts variadic arguments that are concatenated without spaces.
	//
	// Examples:
	//   elem.SetValue("default value")
	//   elem.SetValue("Item ", 42)                // -> "Item 42"
	SetValue(v ...any)

	// Checked returns the current checked state of a checkbox or radio button.
	Checked() bool

	// SetChecked sets the checked state of a checkbox or radio button.
	SetChecked(checked bool)

	// Click registers a click event handler.
	// The handler is automatically tracked and removed when the component is unmounted.
	Click(handler func(event Event))

	// On registers a generic event handler (e.g., "change", "input", "keydown").
	On(eventType string, handler func(event Event))

	// Focus sets focus to the element.
	Focus()
}

Element represents a DOM node. It provides methods for direct manipulation and event binding.

All content methods (SetText, SetHTML, AppendHTML, SetAttr, SetValue) accept variadic arguments and support multiple input types:

  • Strings: Concatenated without spaces
  • Numbers: Converted to strings
  • Format strings: Printf-style formatting with % specifiers
  • Localized strings: Using D.* dictionary for multilingual support

For more information about translation and multilingual support, see: https://github.com/tinywasm/fmt/blob/main/docs/TRANSLATE.md

Examples:

elem.SetText("Hello ", "World")           // -> "Hello World"
elem.SetHTML("<div>", "content", "</div>") // -> "<div>content</div>"
elem.SetAttr("class", "btn-", 42)         // -> "btn-42"
elem.SetText(D.Hello)                     // -> "Hello" (EN) or "Hola" (ES)
elem.SetHTML("<h1>%v</h1>", 42)           // -> "<h1>42</h1>"

func Get added in v0.0.7

func Get(id string) (Element, bool)

Get retrieves an element by its ID.

func QueryAll added in v0.0.11

func QueryAll(selector string) []Element

QueryAll query elements.

type Event

type Event interface {
	// PreventDefault prevents the default action of the event.
	PreventDefault()
	// StopPropagation stops the event from bubbling up the DOM tree.
	StopPropagation()
	// TargetValue returns the value of the event's target element.
	// Useful for input, textarea, and select elements.
	TargetValue() string
	// TargetID returns the ID of the event's target element.
	TargetID() string
}

Event represents a DOM event.

type EventHandler added in v0.2.0

type EventHandler struct {
	Name    string
	Handler func(Event)
}

EventHandler represents a DOM event handler in the declarative builder.

type HTMLRenderer added in v0.0.13

type HTMLRenderer interface {
	RenderHTML() string
}

HTMLRenderer renders the component's HTML structure

type IconSvgProvider added in v0.0.13

type IconSvgProvider interface {
	IconSvg() map[string]string
}

IconSvgProvider is an optional interface for components that provide SVG icons.

type Identifiable added in v0.0.13

type Identifiable interface {
	GetID() string
	SetID(id string)
}

Identifiable provides a unique identifier for a component.

type JSProvider added in v0.0.13

type JSProvider interface {
	RenderJS() string
}

JSProvider is an optional interface for components that need to inject JS.

type Mountable added in v0.2.0

type Mountable interface {
	OnMount()
}

Mountable is an optional interface for components that need initialization logic.

type Node added in v0.2.0

type Node struct {
	Tag      string
	Attrs    []fmt.KeyValue
	Events   []EventHandler
	Children []any // Can be Node, string, or Component
}

Node represents a DOM node in the declarative builder.

type Unmountable added in v0.2.0

type Unmountable interface {
	OnUnmount()
}

Unmountable is an optional interface for components that need cleanup logic.

type Updatable added in v0.2.0

type Updatable interface {
	OnUpdate()
}

Updatable is an optional interface for components that need update logic.

type ViewRenderer added in v0.2.0

type ViewRenderer interface {
	Render() Node
}

ViewRenderer returns a Node tree for declarative UI.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL