Skip to content

Latest commit

 

History

556 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiveTemplate

Reactive web UIs in standard HTML and Go. No custom template language. No client-side framework. No persistent connection required.

📚 Documentation: https://livetemplate.fly.dev — guides, recipes, patterns catalog, full reference.

Quick Start | Docs site | Examples | API Reference

Alpha — Core features work and are tested, but the API may change before v1.0.

LiveTemplate reactive update flow — click a button, server updates state, only changed value sent to browser

The HTML and Go behind a reactive todo list:

<form method="POST">
    <input type="text" name="title" required placeholder="What needs to be done?">
    <button name="add">Add Todo</button>
</form>
<ul>
{{range .Items}}<li>{{.Title}}</li>{{end}}
</ul>
func (c *TodoController) Mount(state TodoState, ctx *livetemplate.Context) (TodoState, error) {
    _ = ctx.Subscribe(ctx.SelfTopic()) // opt in to peer fan-out on this session
    return state, nil
}

func (c *TodoController) Add(state TodoState, ctx *livetemplate.Context) (TodoState, error) {
    state.Items = append(state.Items, Todo{Title: ctx.GetString("title")})
    ctx.Publish(ctx.SelfTopic(), "Refresh", nil) // pushes update to other WS-connected tabs
    return state, nil
}

The button's name IS the action — <button name="add"> routes to Add(). No custom attributes, no JavaScript wiring. Without JS, the form POSTs normally. With the JS client, the DOM patches in place. Add a WebSocket and other tabs sync automatically. See Standard HTML Reactivity for how this compares to htmx, Livewire, and LiveView.

The full todos example uses SQLite, form validation, and toasts. The snippet above is the minimal in-memory shape.

How It Works

sequenceDiagram
    participant Browser
    participant Server

    Browser->>Server: User clicks button<br/>{action: "increment"}
    Note over Server: s.Counter++<br/>(Counter: 5 → 6)
    Note over Server: Tree diff calculated<br/>Only Counter changed → {"0": "6"}
    Server->>Browser: {"0": "6"}
    Note over Browser: DOM updated<br/>Counter: 6
Loading

When a user clicks a button, LiveTemplate calls a method on your Go struct, diffs the template output, and sends only what changed.

Quick Start

go get github.com/livetemplate/livetemplate

1. Define controller and state (full example)

type CounterState struct {
    Counter int
}

type CounterController struct{}

func (c *CounterController) Increment(state CounterState, ctx *livetemplate.Context) (CounterState, error) {
    state.Counter++
    return state, nil
}

func (c *CounterController) Decrement(state CounterState, ctx *livetemplate.Context) (CounterState, error) {
    state.Counter--
    return state, nil
}

func main() {
    controller := &CounterController{}
    state := &CounterState{Counter: 0}
    tmpl := livetemplate.Must(livetemplate.New("counter"))
    http.Handle("/", tmpl.Handle(controller, livetemplate.AsState(state)))
    http.ListenAndServe(":8080", nil)
}

New auto-discovers *.tmpl files in the current directory — counter.tmpl is picked up automatically.

2. Write the template (counter.tmpl)

<h1>Counter: {{.Counter}}</h1>
<form method="POST" style="display:inline">
    <button name="increment">+</button>
    <button name="decrement">-</button>
</form>

<link rel="stylesheet" href="{{lvtClientStyleURL}}">
<script defer src="{{lvtClientScriptURL}}"></script>

lvtClientStyleURL / lvtClientScriptURL are framework-provided template functions that render the CDN URL for the @livetemplate/client release this LiveTemplate version is wire-compatible with — so the browser client stays pinned in lockstep with the server instead of drifting on @latest. To self-host, replace them with your own tag pointing at a vendored @livetemplate/client@<version>.

3. Run it

go run main.go  # Open http://localhost:8080

Features

Standard HTML first — Forms, buttons, dialogs, and links work reactively without custom attributes. lvt-* attributes are available for behaviors HTML can't express (debounce, keyboard shortcuts, reactive DOM). Guide →

Safe state management — Controllers (singleton, hold dependencies) are separated from state (pure data, cloned per session). No accidental data leakage between users. Reference →

Efficient updates — Templates split into static structure (cached) and dynamic values. Updates send only changed values — typically 85%+ bandwidth savings. Details →

Idiomatic Go errors — Actions return (State, error). Validation errors flow to templates automatically. No error serialization code. Error handling →

Code generation — lvt new myapp && lvt gen resource products name price:float scaffolds full CRUD apps with reactive UIs. CLI →

Learn More

Guides:

References:

Related Projects:

  • CLI Tool (lvt) — Code generator and dev server
  • Client Library — TypeScript client (npm: @livetemplate/client)
  • Examples — Counter, Todos, Chat, and more
  • Tinkerdown — Build data-driven apps from a single markdown file (built on LiveTemplate)

Contributing

New to the codebase? Start with the Contributor Walkthrough.

See CONTRIBUTING.md for development setup and guidelines.

License

MIT License — see LICENSE file for details.

About

(Alpha software) Build liveview apps in Go

Resources

Contributing

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages