NAF is a very small set of frontend helpers for fine-grained reactivity, DOM binding, and lightweight template-backed components without a full framework.
This repo now centers on a single merged runtime:
naf.ts: the primary runtimenaf-html.ts: a compatibility export surface for HTML-first imports
Core reactivity:
signal()computed()effect()untrack()setReactiveDebug()getReactiveDebugConfig()
Template and component helpers:
template()when()each()mount()raw()
DOM helpers:
$()$$()listener()$on()fx()show()hide()attr()setText()toggleClass()toggleAttr()text()
Form and list helpers:
model()list()cleanupCollector()collectRowRefs()requireRef()requireElement()
Utilities:
createRouter()
Copy naf.ts into your project and import from it directly.
import {
signal,
template,
mount,
setText,
listener,
cleanupCollector,
} from "./naf";
function Counter() {
const count = signal(0);
return template({
root: ".counter",
onMount(el, _parent, ctx) {
const cleanup = cleanupCollector();
const button = el?.querySelector("button");
const output = el?.querySelector("[data-ref='value']");
cleanup.add(
listener(button, "click", () => count(count() + 1)),
setText(output, () => String(count())),
);
ctx.cleanup.add(cleanup.run);
},
})`
<div class="counter">
<span data-ref="value">0</span>
<button>+</button>
</div>
`;
}
mount(Counter(), document.querySelector("#app"));For existing markup, use the same runtime helpers directly or import them from naf-html.ts if you want the older HTML-first entrypoint:
import { $, fx, list, model, signal } from "./naf-html";
const query = signal("");
model($("input[type='search']"), query, { reactive: true });
fx($(".status"), (el) => {
el.textContent = query().trim() ? `Searching for ${query()}` : "Idle";
});Use template() for bounded shells with local ownership:
- pages
- dialogs
- toolbars
- panels
Use direct DOM plus fx(), model(), listener(), and list() for interaction-heavy surfaces:
- editors
- row renderers
- drag and drop surfaces
- keyboard-driven views
Prefer:
show()/hide()when you are only toggling.hiddenlist()for keyed repeated UI with cleanupcleanupCollector()for one obvious unmount pathdata-refplusrequireRef()inside template-owned markup
The repo still exports naf-html.ts for older examples and import paths. Internally, the source of truth is the merged naf.ts runtime.
The merged runtime keeps older convenience patterns where useful:
$()and$$()support both(selector, root?)and(root, selector)$on()supports both direct element and root-selector formsmodel()supports both direct element and root-selector forms
npm run type-check
npm test