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.
/**
* 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; }
};
}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.
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.
A room-booking system and a property search, both reading and writing Domma CMS collections through the public API.
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.
Seven parts, each with a demo
.value; derived values find their own inputs.
02BindingsTwelve kinds of attribute, switched on in place.
03Keyed listsRows keep their element - and your half-typed input.
04ExpressionsOperators, helpers and literals, parsed by hand.
05ComponentsPrivate state, swappable, with slots.
06ExtendersRate-limit the notification, never the write.
07SafetyMistakes fail closed and name the fix.
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.