Getting started
What Voodoo.js is and when to use it
Voodoo.js is the HTML-first JavaScript framework: you build reactive applications straight in the HTML, describing the behaviour of the page in attributes. It was built for the middle ground: pages that need real interactivity, but that do not justify turning into a whole JavaScript application.
The problem it solves
You want a list that filters, a form that validates and submits over AJAX, a modal and a success message. Four ordinary behaviours, and there are two well-worn roads to get there.
Down the jQuery road, each one turns into a handful of addEventListener calls,
selectors and manual DOM work. It works, and six months later nobody knows which snippet writes
into which element.
Down the Vue or React road, each one turns into a component — and with it come a build step, a bundler, a config file and a dependency folder weighing hundreds of megabytes. For one page. That was already HTML.
Voodoo's answer is the third road: the attributes describe the behaviour, and the library takes care of the rest.
| What you want | What you write |
|---|---|
| A counter that reacts | <button @click="n++"> |
| Show it when signed in | <div v-show="loggedIn"> |
| Repeat a list | <li v-for="u in users"> |
| A field bound to state | <input v-model="search"> |
| Load data from an API | <div v-get="/api/users" v-target="#list"> |
| A validated AJAX form | <form v-submit="/api/users" v-validate> |
| A CPF mask | <input v-mask="cpf"> |
| Confirm before deleting | <button v-delete="/api/x" v-confirm="Delete?"> |
| A success message | <button @click="$toast.success('Saved!')"> |
| A sound on click | <button v-sound="click"> |
| A line chart | <div v-chart="{ type: 'line', data: sales }"> |
| State that survives F5 | <div v-data="{...}" v-persist="draft"> |
What it does differently
The HTML ends up clean
Once a directive has been processed, the v-* attribute leaves the document. In the
browser inspector you see <button>Save</button>, not
<button v-click="save()" v-loading="#spin">. The behaviour keeps working
because the value is held in the runtime.
Never write CSS or a querySelectorAll that leans on selectors like
[v-tab]: by the time you look, the attribute is already gone. Use classes or
data-*, which stay. If you need the old behaviour, you can turn this off in
V.config.cleanAttributes.
No eval and no new Function
Attribute expressions go through a lexer, a Pratt parser and a tree interpreter written by hand.
The library runs under a restrictive Content Security Policy, with no unsafe-eval.
That also explains why an expression accepts less than full JavaScript: there is no
function, there is no new, and the globals it exposes are a closed
list.
Granular updates, with no Virtual DOM
Tracking is per key. When count changes, only the effects that read
count run again, and each effect writes only into the node it created itself. There
is no virtual tree to diff, and no whole-component render because of one number.
Interpolation with a single brace
{ variable } is the standard form. {{ variable }} is accepted too, for
anyone coming from Vue whose fingers are already trained.
<div v-data="{ n: 3 }">
<p>One brace: { n * 2 }</p>
<p>Two braces: {{ n * 2 }}</p>
</div>
Zero runtime dependencies
No React, Vue, lodash, jQuery or Axios underneath. Only browser APIs. The file you download is the whole project.
Who it is for
- Admin panels rendered on the server with Laravel, Rails, Django, Spring or plain PHP.
- Landing pages and content sites that need a bit of life without loading a whole framework.
- Prototypes, where opening a file and seeing the result is worth more than any architecture.
- Small teams that do not want to maintain a build pipeline just to show a table.
- Legacy projects, where the library lives alongside the code that is already there, because it never takes over the whole page.
When not to use it
Worth saying plainly, because picking the wrong tool is expensive later.
- A very large application, with dozens of screens and a large team. Single-file components, nested routing and compile-time type checking are real advantages of Vue, React and Svelte at that size.
- You need server rendering with hydration. Voodoo runs in the browser. It sits happily next to HTML that came from the server, but it hydrates nothing. The pure modules (reactivity, HTTP, utilities) do work in Node.
- A native mobile app. There is no React Native equivalent here.
-
Lists of tens of thousands of rows updating at once.
v-forreuses elements by key, but it does not virtualize. - You want the smallest possible file and you only need two or three behaviours. Alpine.js may be the better choice.
- Your team already knows another framework and the project is already standing. Swapping for the sake of swapping does not pay.
An honest comparison
| Voodoo.js | Alpine.js | HTMX | Vue 3 | React 19 | jQuery | |
|---|---|---|---|---|---|---|
| Gzipped size | 42 to 124 KB | 15 KB | 14 KB | 34 KB | 45 KB | 30 KB |
| Needs a build | No | No | No | Recommended | Yes | No |
| Reactivity | Yes | Yes | No | Yes | Yes | No |
| Components | Yes | Limited | No | Yes | Yes | No |
| Declarative HTTP | Yes | No | Yes | No | No | No |
| Forms and validation | Included | No | No | Library | Library | Plugin |
| Field masks | Included | No | No | Library | Library | Plugin |
| UI components | 29 ready-made | No | No | Library | Library | jQuery UI |
| Charts | Included | No | No | Library | Library | Plugin |
| Spring animation | Included | No | No | Library | Library | Basic |
| Router | Included | No | Partial | Official | Library | No |
| Works under a strict CSP | Yes | No | Yes | With a build | Yes | Yes |
| Ecosystem | New | Medium | Medium | Huge | Huge | Huge |
The honest reading: Alpine and HTMX are smaller because they do less. Vue and React have an ecosystem, tooling and a community that Voodoo does not have. What Voodoo delivers, in one file, is the set you would normally assemble out of five libraries.
What does not exist yet
This list is here so that nobody goes hunting through the documentation for something that has not been delivered.
- Server rendering and hydration. Today the library only mounts in the browser.
- List virtualization.
v-forrenders every item in the source. - A browser extension for the devtools. The
xrayinspector runs inside the page itself. - Nested routes.
v-router-viewrenders a single outlet, with no hierarchy. - Per-route transitions of its own. The router uses the View Transitions API when the browser offers it, and nothing beyond that.
- Ready-made date and upload components. There are
VInputandVSelect, but not a date picker yet. - Generated types for templates. Attribute expressions have no static checking.
The common problems page lists, separately, the known details that are waiting on a fix and how to work around each one in the meantime.