Overview
1.1 What kerf is
Section titled “1.1 What kerf is”A tiny reactive UI framework. Roughly 11 KB minified + gzipped including its sole runtime dependency (@preact/signals-core); ~12 KB if you also import arraySignal from the optional kerfjs/array-signal subpath. Four primitives:
- Signals — fine-grained reactive values.
signal(),computed(),effect(),batch(). - Stores — composable testable units of state.
defineStore(),resetAllStores(). - Render —
mount(rootEl, () => jsx). JSX renders to a structuredSafeHtml; kerf’s segment-aware diff reconciles static surrounds while a keyed list reconciler owns rows fromeach(...). The split keeps partial-update / select-row / swap-rows costs at O(changes), not O(rows). - Event delegation —
delegate()(Tier 1, bubble) anddelegateCapture()(Tier 2, capture) replace per-element listeners with one root-level listener per event type.
Plus a JSX runtime (kerfjs/jsx-runtime) and an SVG-aware toElement() for direct JSX-to-DOM conversion.
1.2 What kerf is NOT
Section titled “1.2 What kerf is NOT”- Not a component framework.
<MyComponent props />works as JSX sugar — the runtime callsMyComponent(props)and uses the returned JSX — but there’s no per-instance state, no hooks, and no lifecycle. Components are plain functions; state lives in module-scope signals or stores. - Not a router. Not a build tool. Not an SSR framework (though
SafeHtml.toString()works server-side if you want it). - Not opinionated about styling. Bring your own CSS.
- Not magical. There’s no compiler, no virtual DOM, no scheduler, no concurrent rendering, no hooks model, no lifecycle. The “no compiler” rule is non-negotiable — kerf will not ship an opt-in codegen package either. If you want compile-time fine-grained reactivity, pick Solid; that’s Solid’s value proposition, and Solid does it better than a kerf-compiler ever could. Kerf’s positioning is “the fastest framework that needs no build step beyond your existing one,” which means accepting Solid’s architectural-floor numbers on update-path benchmarks (~6ms select-row, ~20ms partial-update) as the ceiling. The goal is to close the runtime-vs-compiled gap on every benchmark kerf can close without a compiler — not to match Solid on the ones that require one.
1.3 When to reach for kerf
Section titled “1.3 When to reach for kerf”kerf is a good fit when:
- You want fine-grained reactivity without buying into a framework’s full mental model.
- Your app is server-rendered HTML + interactive islands, and you want a tiny client-side enhancement layer.
- You care about preserving live DOM identity across re-renders (focus, selection, in-flight pointer interactions, third-party widget instances).
- Your users include people who run with assistive tech, where DOM identity preservation matters more than it does in benchmark loops.
It’s a poor fit when:
- You need a full ecosystem of components, routers, devtools, SSR primitives. Use React / Vue / Solid.
- Your team is heavily invested in a framework’s conventions. The cost of switching outweighs the runtime size win.
- You want compile-time JSX transforms that produce optimal DOM ops directly. Use Solid.
1.4 Mental model
Section titled “1.4 Mental model”The runtime answers two questions:
-
WHEN do we re-render? Answered by signals: an
effect()re-runs whenever any signal it read during its last run changes.mount()wrapseffect()so that re-renders happen automatically when the JSX you return depends on a signal that changes. -
HOW do we re-render? Answered by kerf’s morph: render JSX to a
SafeHtml(which is a string for static content and a structured tree where lists or list-containing parents appear), then walk the live DOM in lock-step. Static surrounds go through a general-purpose tree-morph (src/morph.ts, also exported asmorph()for one-shot consumer use); list contents go through a keyed reconciler (each(...)’s side ofmount) that operates directly on live children — no parse-the-whole-list step. Element identity is preserved wherever the morph matches by key (id,data-key) or position.
Everything else is detail.
1.5 The architecture in one diagram
Section titled “1.5 The architecture in one diagram” user code ───────────────────────────────────────────── const count = signal(0);
mount(rootEl, () => ( ← effect() wrapper <div> <button data-action="inc">+</button> ← Tier 1 delegation target <span>{count.value}</span> ← signal read tracked </div> ));
delegate(rootEl, 'click', '[data-action="inc"]', () => { count.value += 1; ← signal write triggers re-run }); ───────────────────────────────────────────── │ │ count.value++ ▼ ┌─────────────────────────────────────────┐ │ effect() fires the render fn │ │ → SafeHtml (segment tree) │ │ → morph(live, template, ownedItems) │ │ → each() reconciler patches each list │ │ → minimal DOM mutations applied │ └─────────────────────────────────────────┘1.6 Reading order for the rest of the docs
Section titled “1.6 Reading order for the rest of the docs”- §2 Reactivity — signals primitive.
- §3 Stores — composable testable stores.
- §4 Render — mount, segments, the native diff, and the list reconciler.
- §5 Event delegation — Tier 1 / Tier 2 / Tier 3.
- §6 JSX runtime — JSX → HTML strings, server-side use.
- §7 SVG — namespace handling and the
toElement()escape hatch. - §8 API reference — every export, every option.