Skip to content

Migrating to Kerf

Kerf doesn’t try to replace your framework. But if you’ve already decided to leave one — or you’re building something new and weighing the cluster — these pages show your current code translated, line for line, into kerf.

The reference app is a classic todo list: add, toggle, delete, filter. Roughly 150 lines in every framework. Big enough to exercise state, keyed lists, delegated events, and persistence. Small enough that you can read the whole thing.

How kerf compares to each source framework on the dimensions developers usually decide on.

DimensionReact 19Alpine 3Lit 3vanjs 1.5Kerf
Bundle (min+gz, runtime only)~45 KB (react + react-dom)~14 KB~6 KB (lit-html + lit-element)~1.6 KB~6.5 KB (incl. signals)
Reactivity modelhooks + virtual DOMper-component x-data proxiesreactive properties + lit-htmlsignals + direct DOMsignals + DOM morph
Component modelfunction / class componentsHTML directivesweb componentshyperscript factoriesplain functions returning JSX
TemplatingJSX → virtual DOMHTML attributestagged-template literalshyperscript / DOM nodesJSX → HTML strings → morph
Compiler requiredno (but @vitejs/plugin-react)nononono
Keyed list reconciliationkey prop, virtual-DOM diffmanual via x-for :keyrepeat() directivemanual via signal arrayseach(items, render, key)
Focus / selection survival across re-rendermanual via refsmanualmanualmanualautomatic in morph()
Event handlingper-node JSX handlers@click= attributes@click= template directiveshyperscript handlersdelegate() once on the root
Server renderingfirst-classnonedeclarative shadow DOMvanX.replace storySafeHtml.toString()
State sharing across componentsContext / Redux / ZustandAlpine.storeglobal state libsshared van.state()defineStore

Numbers from bench/results.md (krausest js-framework-benchmark, medians of 3 runs, ms — lower is better).

OpReact 19Lit 3vanjs 1.5Kerf 0.5
create 1k40.938.546.646.1
partial update24.121.941.844.6
swap rows157.328.923.722.3
select row8.09.314.327.6
remove row18.018.318.317.0

Alpine isn’t in the krausest benchmark; it’s not designed for keyed-list throughput so the comparison wouldn’t be apples-to-apples.

How the per-framework pages are structured

Section titled “How the per-framework pages are structured”

Each page is the same shape so you can scan for what you care about:

  1. Bundle delta — what the swap costs (or saves) in bytes.
  2. Mental-model translations — a table mapping the source framework’s primitives to kerf’s. React useState → kerf signal. Lit @property → kerf signal. Alpine x-data → kerf defineStore.
  3. Side-by-side code — the same todo list, side by side, section by section (state, render, events, list). Click any code block to see it full-width.
  4. Gotchas — what trips developers coming from this framework. React devs expect <Component /> semantics; kerf doesn’t have them. Alpine devs expect DOM-attribute reactivity; kerf renders JSX. Lit devs expect Shadow DOM; kerf is light-DOM.
  5. Perf numbers — krausest benchmark deltas for the operations that change most when you swap.

If you’re coming from Vue, Svelte, Solid, or Preact — the React page is the closest fit in spirit (signals/hooks/keyed lists are the same shape). For framework-free vanilla JS, the vanjs page is closest (no template language, direct DOM).

If your app is built around heavy component composition (<DataGrid>, <DatePicker>, deep prop drilling), kerf probably isn’t your next stop — see When to reach for something else.