The data changes. The page follows.

domma-reactive keeps HTML in step with your data - on the page your server already rendered.

You write the relationship down once. It is maintained from then on.

Install it Build something with it

New in 1.2 Bindings that fail safe - a bare observable now stays hidden and says why - and object and array literals in expressions. What's new →

You write data-bind-text="user.name" in your markup, or computed(() => price.value * qty.value) in your code, and that is the last time you think about it. Change the data; exactly the parts of the page that depend on it update.
There is no update code to write, no re-render to trigger, and no build step - a <script> tag on a server-rendered page is enough.

This site is that claim, demonstrated. Every counter, list, search box and booking form below is running the real library against real content in this Domma CMS, and every code listing is read from the file that is running.

Ten seconds of it

0

Doubled
0
Parity
even
Effect runs
1

Nothing else on this page re-rendered. Three bindings read count; three bindings ran.

Three bindings read count. Pressing a button re-runs those three and nothing else on this page.

apps/basics.js - the whole counterview whole file
/**
 * Counter - `observable`, `computed`, `effect`, and what "fine-grained" means.
 *
 * Three bindings read `count`. Pressing +1 re-runs those three and nothing
 * else on the page, because each binding owns its own effect and each effect
 * knows exactly what it read.
 */
function counter() {
    const count = observable(0);
    const runs = observable(0);

    // A computed works out its own inputs at runtime. Neither of these is told
    // that it depends on `count`; both discover it by reading it.
    const doubled = computed(() => count.value * 2);
    const parity = computed(() => (count.value % 2 === 0 ? 'even' : 'odd'));

    // An effect is the same machinery pointed at a side effect. `peek()` reads
    // without subscribing - reading `runs.value` here would make this effect
    // depend on its own write and spin forever.
    effect(() => {
        count.value;
        runs.value = runs.peek() + 1;
    });

    return {
        count, doubled, parity, runs,
        step(by) { count.value += by; },
        reset() { count.value = 0; }
    };
}
What is it?

A reactivity graph and twelve binding kinds. Observables, computeds that work out their own inputs, and attributes that live in your HTML - dropped into a page you already have rather than taking it over.

About

Learn it

A working contacts page in twelve steps: add, search, filter, edit in place, delete, remember across a reload, and extract the row into a component.

Tutorial

Show me something real

A room-booking system and a property search, both reading and writing Domma CMS collections through the public API.

Examples

Install it
npm install domma-reactive

Or one script tag from a CDN - there is no build step either way. MIT, no dependencies, about 21 KB gzipped.

Setup guide Links

Where it lives

Published on npm as domma-reactive, MIT, no dependencies. The source, the issues and the full history are on GitHub.

npm GitHub

Seven parts, each with a demo

Why "no eval" matters

Knockout, Alpine's standard build and petite-vue turn binding strings into code with the Function constructor. A Content Security Policy of script-src 'self' - the one security teams ask for - blocks that outright unless you allow unsafe-eval. domma-reactive parses its expressions into a tree and walks it instead, so it runs under a strict policy with its only build and no special mode. How it compares.

The short version

Version 1.2.0 - what's new
Size About 21 KB gzipped, no dependencies, MIT, TypeScript declarations included
Build step None. A <script> tag, or an import from any bundler
Content Security Policy Runs under script-src 'self' - no eval, no Function constructor anywhere
Server rendering applyBindings() activates markup in place and never rewrites your page
Lists Reconciled by key, so rows keep their DOM - focus, scroll and half-typed input survive
Failure One broken binding logs one warning and is skipped. The rest of the page keeps working

Where it sits

domma-reactive is the reactive core of Domma JS, published separately so it can be used on its own - load Domma and it is already there as M. On this site it runs alongside both halves of the stack it came from: Domma JS supplies the HTTP client, the toasts, the dates and the theme; Domma CMS stores every room, booking and property listing as a collection and publishes it at /api/v1/<slug> with no endpoint written by hand.

It is not a framework. There is no router, no lifecycle hooks beyond a component's dispose(), no virtual DOM and no devtools. It is the layer beneath those - and About makes the case for wanting exactly that.