talkDOM: A Tiny Message-Passing Runtime for the DOM

talkDOM is a lightweight framework for declarative DOM messaging. HTML elements send Smalltalk-style messages to fetch content, update the DOM, compose pipelines, and control navigation—all with a tiny runtime and no build step.

Modern frontend development often assumes that building interactive web interfaces requires a heavy JavaScript framework. Over the years, frameworks have grown increasingly complex, introducing components, state management systems, build pipelines, and large runtime bundles. https://github.com/eringen/talkdom

But the web already has a powerful platform: HTML + the DOM + HTTP.

The idea behind talkDOM is simple:

What if DOM elements could send messages to each other?

Instead of wiring JavaScript handlers everywhere, HTML becomes a small message-passing language. Elements act as senders and receivers, and interactions are expressed declaratively.

The result is a tiny runtime (only a few kilobytes) that enables fetching data, updating DOM fragments, composing pipelines, polling endpoints, and controlling navigation — all from HTML.


The Core Idea

In talkDOM, elements communicate using Smalltalk-style messages.

A sender contains a sender attribute:

html
<button sender="posts get:/posts | list apply:inner">
  Load Posts
</button>

And somewhere in the page there is a receiver:

html
<div receiver="list"></div>

When the button is clicked:

  1. A message is sent to posts
  2. A GET request is performed
  3. The response is piped to list
  4. The list's content is replaced

This message:

posts get:/posts | list apply:inner

can be read almost like a sentence:

"Posts, get /posts, then list apply the result as inner content."

Message Pipelines

One of the key features of talkDOM is message pipelines.

Multiple operations can be composed using |:

posts get:/posts | transform apply:json | list apply:inner

Each step can return a value (or a promise), which is passed to the next step in the pipeline.

This design is inspired by:

* Unix pipes * Smalltalk keyword messages * message passing systems

Pipelines allow complex UI behaviors to be expressed with surprisingly little code.


Receivers

Receivers are simply DOM elements with a receiver attribute.

html
<div receiver="posts"></div>

Receivers can also belong to multiple groups:

html
<div receiver="toast error"></div>

Messages sent to toast will target all matching elements.

Receivers can optionally restrict what operations they accept:

html
<div receiver="posts" accepts="inner append text"></div>

This adds a small layer of safety to DOM updates.


Fetching and Updating Content

talkDOM provides built-in methods for HTTP requests and DOM updates.

Example:

html
<button sender="posts get:/posts apply:inner">
  Load
</button>

<div receiver="posts"></div>

Supported request methods:

* get: * post: * put: * delete:

And update operations:

* inner * append * text * outer


Server-Triggered Actions

Servers can trigger client-side actions using a response header:

X-TalkDOM-Trigger

Example response header:

X-TalkDOM-Trigger: toast apply:"Saved!" text

This allows the server to instruct the client to update UI elements without embedding JavaScript.


Polling

Receivers can automatically poll an endpoint.

html
<div receiver="feed get:/posts poll:5s"></div>

This will fetch /posts every 5 seconds and update the receiver.

Polling stops automatically if the element is removed from the DOM.


Navigation and History

talkDOM also supports history-aware navigation.

html
<a sender="page get:/posts apply:inner" push-url="/posts">
  Posts
</a>

When clicked:

  1. The request is executed
  2. The content is updated
  3. The URL is pushed to browser history

Back/forward navigation replays the same message chain.


Persistence

Receivers can optionally persist their content in localStorage.

html
<div receiver="cart" persist></div>

After page reload, the content is restored automatically.


Events

talkDOM emits lifecycle events after operations:

* talkdom:done * talkdom:error

Example:

javascript
document.addEventListener("talkdom:done", e => {
  console.log("operation finished", e.detail);
});

This makes it easy to hook into the runtime when needed.


Why Build This?

Projects like HTMX, Unpoly, and Turbo have shown that many interactive interfaces can be built without large frontend frameworks.

talkDOM explores a slightly different direction.

Instead of attribute-based APIs like:

hx-get="/posts"
hx-target="#list"
hx-swap="inner"

talkDOM uses a message syntax:

posts get:/posts apply:inner

This makes interactions:

* composable * pipeline-friendly * closer to a programming language

HTML becomes a small DSL for UI behavior.


Design Goals

talkDOM was built with a few goals in mind:

* Tiny runtime * No build step * Progressive enhancement * Composable interactions * Minimal JavaScript

The entire runtime is just a few hundred lines of JavaScript.


What This Is (and Isn’t)

talkDOM is not trying to replace full UI frameworks.

It is best suited for:

* server-rendered applications * dashboards * admin panels * progressively enhanced pages

Where the server remains the primary source of truth.


Closing Thoughts

The web already provides a powerful programming model:

* documents * events * HTTP * the DOM

Sometimes the best tool is not another abstraction layer, but a small runtime that lets the platform speak for itself.

talkDOM is an experiment in that direction: turning HTML into a tiny message-passing language for building interactive interfaces.

The project is available on GitHub:

https://github.com/eringen/talkdom

Also currently in use in here!