The smallest cut.
Introducing Kerf. The smallest cut.
6.6 KB. No virtual DOM. No compiler. No magic. Reactive UI that touches only the bytes that changed.
import { signal, mount } from 'kerfjs';
const count = signal(0);
mount(document.getElementById('app')!, () => (
<div>
<button data-action="inc">+</button>
<span>{count.value}</span>
</div>
));That's it. Your JSX renders to HTML strings, kerf's native diff applies the minimum DOM mutations to make the live tree match, and signals re-run the render only when something they read actually changed.
-
Built for the AI-assisted era. Tiny public surface (15 exports), no compiler magic, no hidden lifecycle. An LLM holds the framework in context and predicts behaviour — your AI agent generates code that works the first time. Ships
llms.txtand a dedicated AI usage guide. -
Smallest cut. 6.6 KB gzipped including signals. Fine-grained reactivity re-runs only what changed; the diff touches only the DOM nodes that differ.
-
No virtual DOM, no compiler. JSX → HTML strings → native diff. DevTools shows the real DOM because it is the DOM.
-
Focus, selection, listeners survive re-renders. We morph instead of rebuilding — your caret stays where you put it, your in-progress drag keeps moving, your delegated handlers keep firing.
-
Plain TS, plain JSX, plain ESM. Drops into anything using esbuild / Vite / tsup. No plugin chain.
- AI-generated apps — your LLM/agent holds the framework in context; no hallucinated APIs.
- Hybrid desktop apps (Tauri / Electron) — small bundle, predictable diff, debuggable runtime; ideal for the embedded webview.
- Embedded widgets — chat bubbles, comment boxes, dashboards dropped into someone else's page.
- Server-rendered apps with islands — Rails / Phoenix / Django / Hono.
mountper island;delegatesurvives turbo-frame swaps. - Admin panels & internal tools — reactivity without 200 KB of framework + state lib + router.
- Replacing jQuery — incremental migration; same delegation mental model, modern primitives.
- Prototyping — entire mental model on a postcard.
- Need a full ecosystem (router + forms + data + SSR streaming) → Next.js / Remix / SolidStart.
- Building a deeply componentised design-system app → React / Solid / Svelte.
- Need React Native / cross-platform mobile → React (Kerf + Tauri/Electron also covers many of these cases).
- Building a static site → Astro (we use it for this project's site).
import { signal, computed, effect, defineStore, mount, each, delegate } from 'kerfjs';
// 1. A signal — single piece of reactive state.
const count = signal(0);
// 2. A computed — auto-derived from other signals.
const doubled = computed(() => count.value * 2);
// 3. A store — multi-consumer state with named actions and reset semantics.
const cart = defineStore({
initial: () => ({ items: [] as { id: string; name: string }[] }),
actions: (set, get) => ({
add: (id: string, name: string) => set({ items: [...get().items, { id, name }] }),
remove: (id: string) => set({ items: get().items.filter((i) => i.id !== id) }),
}),
});
// 4. Mount JSX to a DOM element. Re-renders only when read signals change.
const root = document.getElementById('root')!;
mount(root, () => (
<div>
<h1>Cart ({cart.state.value.items.length})</h1>
<ul>
{each(
cart.state.value.items,
(item) => (
<li>
{item.name}
<button data-action="remove" data-id={item.id}>×</button>
</li>
),
(item) => item.id,
)}
</ul>
<p>Doubled count: {doubled.value}</p>
</div>
));
// 5. Event delegation — one listener per event type, dispatched by data-action.
delegate(root, 'click', '[data-action="remove"]', (_e, btn) => {
cart.actions.remove((btn as HTMLElement).dataset.id!);
});npm install kerfjs- Site: brianwestphal.github.io/kerf
- Docs:
docs/— overview · reactivity · stores · render · events · jsx · svg · API reference - AI guide:
docs/ai/usage-guide.md— read once before writing kerf code with an LLM - Demo: live demo — seven sections exercising every primitive
- Repo: github.com/brianwestphal/kerf
A kerf is the narrow strip of material a saw blade removes when cutting — the smallest possible cut. The framework's job is the same: apply the smallest possible mutation to update your DOM.
(And yes, kerformance → performance jokes were written. They were also rejected.)
v0.3.x — early. API may evolve. See CHANGELOG.md for what's shipped.
MIT