The framework that stays in your HTML

Reactivity, components, HTTP, forms and a UI kit, written as attributes on the markup you already have. One script tag. No build step, no configuration, no dependencies at runtime.

And JSX, in a plain .html file. No compiler, no bundler, no transform. { fruits.map(f => (<li>{ f }</li>)) } written straight into the markup, reactive, under a strict CSP. Try it in the playground.

<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>
Running on this page, right now
<div v-data="{ n: 0 }">
  <output>{ n }</output>

  <button @click="n--">-</button>
  <button @click="n++">+</button>

  <p>double: { n * 2 }</p>
</div>
0

double: 0 · 0 clicks so far

Why

Three things it does differently

Nothing to compile

The script tag is the whole installation. No bundler, no transpiler, no config file, no JSX. Open the HTML file and it works, including from file://.

It cleans up after itself

Once a directive is installed, its attribute is read into memory and removed from the document. What ships to the page is ordinary HTML, with no framework residue in the inspector.

No eval, anywhere

Expressions run through a lexer, a Pratt parser and an interpreter, never eval or new Function. That is why it works under a strict CSP with no unsafe-eval, proven by a browser test.

The DOM afterwards

Your markup does not become framework output

Inspect the paragraph below with your browser's dev tools. The attributes that made it reactive are gone from the document; the binding is still live.

What you write
<p class="greeting"
   v-data="{ name: 'Vudu' }"
   v-text="'Hello, ' + name"></p>
What stays in the DOM
<p class="greeting">Hello, Vudu</p>

Same counter

The same feature, four ways

A counter with two buttons, a derived value and a screen that keeps up. The result is identical in all four. What changes is how much you write, and how much you install before you start.

<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>

<div v-data="{ n: 0 }">
  <output>{ n }</output>
  <button @click="n--">-</button>
  <button @click="n++">+</button>
  <p>double: { n * 2 }</p>
</div>

7 lines0 build steps0 dependencies

<div>
  <output id="out">0</output>
  <button id="dec">-</button>
  <button id="inc">+</button>
  <p id="dbl">double: 0</p>
</div>

<script>
  let n = 0;
  const render = () => {
    out.textContent = n;
    dbl.textContent = 'double: ' + n * 2;
  };
  inc.onclick = () => { n++; render(); };
  dec.onclick = () => { n--; render(); };
</script>

18 lines0 build stepsevery update wired by hand

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<div id="app">
  <output>0</output>
  <button class="dec">-</button>
  <button class="inc">+</button>
  <p class="dbl">double: 0</p>
</div>

<script>
  let n = 0;
  function render() {
    $('#app output').text(n);
    $('#app .dbl').text('double: ' + n * 2);
  }
  $('#app .inc').on('click', () => { n++; render(); });
  $('#app .dec').on('click', () => { n--; render(); });
</script>

19 lines1 dependencystill re-renders by hand

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<div id="app">
  <output>{{ n }}</output>
  <button @click="n--">-</button>
  <button @click="n++">+</button>
  <p>double: {{ n * 2 }}</p>
</div>

<script>
  Vue.createApp({
    data: () => ({ n: 0 })
  }).mount('#app');
</script>

13 lines1 dependencya mount call, and an app root

Voodoo did not invent reactivity. It put reactivity back inside the HTML without charging a build step for it.

Measured

How it performs against the others

Seven implementations of the same 1,000-row list, all bundled production and minified, run back to back in one process against the same document. Median of 30 samples, in milliseconds, lower is better. After each run every framework's DOM is compared with the hand-written baseline, so anything producing different output is excluded rather than credited with a fast time.

Framework Create 1k Update 1 in 10 Clear 1k
vanilla JS 39.51 6.65 20.04
Preact 10.29.8 71.03 2.73 30.68
Voodoo.js 0.13.0 77.47 4.69 30.14
Vue 3.5.42 78.72 14.29 32.84
Solid 1.9.15 80.13 0.90 21.85
React 19.2.8 81.22 4.65 33.55
Alpine 3.17.1 157.06 111.29 32.76

Read honestly: on create Voodoo is third of seven, behind hand-written vanilla and Preact, and vanilla still builds a list twice as fast as we do. On update we are now faster than that hand-written baseline — 4.69 ms against 6.65 — and level with React, though Solid and Preact are ahead and they get there with a compiler. The update column is the noisiest of the three and should be read as a range rather than a ranking. Voodoo is also by far the largest bundle in this table, and if size is your main constraint, Alpine and Preact are the honest recommendation.

Get started

Two ways in, both about a minute

A script tag
<script src="https://cdn.jsdelivr.net/npm/voodoojs@0.13.0/dist/voodoo.full.min.js" defer></script>

That is the entire installation. The library starts itself once the page is ready. Pinned to the 0.5 line, so patches arrive without an edit.

npm
npm install voodoojs

ESM and CJS with types, plus separate entry points such as voodoojs/reactivity and voodoojs/http when you want only a piece.

In the box

Twenty-nine components you do not install

They are custom elements the library defines when it starts, so writing the tag is the whole usage. Everything below is running here, on this page.

What you write
<v-button variant="primary">Save</v-button>
<v-badge tone="success">Active</v-badge>
<v-progress value="70"></v-progress>
<v-rating value="4"></v-rating>
Running right here
Save Active

Dogfood

This site is built with Voodoo.js

Not a marketing line. The page you are reading has no build step and no framework code of its own. The tabs above, the counter in the hero, the language picker, the theme toggle, the mobile menu and the Ctrl+K palette are all directives on this HTML, running from the same voodoo.full.min.js the install section hands you.

View source, or press Ctrl+K and pick "View this page's source". If the framework could not carry a real site, you would find out here first.

1,197
unit tests, plus 44 in real Chromium
0
runtime dependencies
46 KB
core build, gzipped
0
build steps, here or in your project