The 4KB DOM Framework
You Didn't Know You Needed

xxdom.js is a zero-dependency, single-file JavaScript library for declarative DOM rendering. No build step. No virtual DOM. No complexity. Just add a script tag and go.

~4KB min+gzip Zero Dependencies ~19ms / 1K rows MIT Licensed v0.2.0
<!-- That's it. No build step. -->
<script src="https://unpkg.com/xxdom/dist/xx.min.js"></script>

<div xx-scope="{ name: 'World' }">
  <input xx-model="name">
  Hello {{name}}!
</div>
~0
Lines of Code
~0KB
Min + Gzip
0
Dependencies
~0ms
Create 1K Rows

Everything you need.
Nothing you don't.

{ }

Declarative Directives

Familiar xx-for, xx-if, xx-model, xx-on syntax inspired by Vue.js. If you know Vue, you already know xxdom.

</>

Mustache Interpolation

Use {{ "{" + "{expression}" + "}" }} anywhere in your HTML. Full JavaScript expressions — math, functions, ternaries, whatever you need.

Incremental Rendering

Smart change detection updates only what changed. Key-based list diffing preserves DOM state across reorders.

📦

Components

Define reusable xx-component templates. Attribute merging, class concatenation, and dynamic template selection via xx-tmpl.

Lifecycle Hooks

xx-init, xx-mounted, xx-updated, xx-destroy — hook into every stage of an element's life.

Async / Suspense

xx-await with xx-pending, xx-then, xx-catch slots. Handle promises declaratively without callback spaghetti.

Strengths & Limitations

Strengths

Truly zero-config. One script tag. No npm, no webpack, no Vite, no JSX. Works in any HTML file, even opened from the filesystem.
Incredibly small. ~4KB min+gzip. Smaller than most favicons. Loads in milliseconds on any connection.
Familiar syntax. If you know Vue.js, Alpine.js, or Angular, the directive syntax will feel natural immediately.
Full feature set. For-loops with diffing, conditionals, two-way binding, events with modifiers, async/Suspense, components, lifecycle hooks, and SSR — all in ~1,400 lines.
Predictable rendering. You call xx(), it renders. No hidden reactivity magic, no stale closure surprises, no "why did this re-render?".
Great for learning. The entire source is readable in one sitting. Perfect for understanding how a DOM framework works under the hood.

Limitations

!
No automatic reactivity. You must call xx() manually after data changes. This is a deliberate trade-off for simplicity, but requires discipline.
!
CSP incompatible. Uses Function() constructor for expressions, which requires unsafe-eval. Not suitable for strict Content Security Policy environments.
!
No ecosystem. No router, no state management library, no component marketplace. You're on your own for complex app architecture.
!
Not for large SPAs. Designed for small-to-medium interactive pages, not full single-page applications. Use Vue/React/Svelte for those.
!
No slot/content projection. Components can't accept child content. You get attribute merging but no <slot> equivalent.
!
Small community (so far). This is an early-stage project. You'll be a pioneer, not a follower. That's either exciting or risky, depending on your perspective.

How does xxdom.js compare?

Real, measured performance across standard framework benchmark criteria. xxdom.js numbers are from our reproducible benchmark suite. Other framework data sourced from js-framework-benchmark (keyed, Chrome 120+).

Visual Performance Comparison

Lower is better. Bars scaled relative to slowest framework in each category.

Create 1,000 Rows
Vanilla JS
3ms
Svelte
5ms
Lit
8ms
Vue 3
12ms
xxdom.js
19ms
Petite-Vue
32ms
Alpine.js
45ms
HTMX
N/A
server
Partial Update (every 10th row of 1,000)
Vanilla JS
1ms
Svelte
2ms
Lit
3ms
xxdom.js
5ms
Vue 3
6ms
Petite-Vue
8ms
Alpine.js
18ms
Swap Two Rows (in 1,000 row table)
Vanilla JS
<1ms
Svelte
1ms
Lit
2ms
Vue 3
3ms
xxdom.js
4ms
Petite-Vue
7ms
Alpine.js
15ms
Clear All 1,000 Rows
Vanilla JS
<1ms
Svelte
1ms
xxdom.js
1.4ms
Lit
2ms
Vue 3
3ms
Petite-Vue
4ms
Alpine.js
8ms
xxdom.js
Alpine.js
Petite-Vue
Vue 3
Svelte
Lit
HTMX
Vanilla JS

Complete Framework Comparison

Every criterion that matters, side-by-side. Scroll horizontally on mobile.

Criterion xxdom.js Alpine.js Petite-Vue Vue 3 Svelte 5 Lit HTMX Stimulus Vanilla JS
Size & Setup
Min + Gzip 5.2 KB 14.4 KB 6.4 KB 33 KB 2.1 KB* 5.8 KB 14 KB 7 KB 0 KB
Dependencies 0 0 0 3 0 0 0 2 0
Build step No No No Yes Yes No No Optional No
CDN script-tag install Yes Yes Yes Partial No Yes Yes Yes Yes
Runtime Performance (keyed, 1K rows)
Create 1,000 rows ~19 ms ~45 ms ~32 ms ~12 ms ~5 ms ~8 ms server N/A ~3 ms
Replace all rows ~7 ms ~50 ms ~35 ms ~14 ms ~5 ms ~9 ms server N/A ~3 ms
Partial update (10th row) ~5 ms ~18 ms ~8 ms ~6 ms ~2 ms ~3 ms server N/A ~1 ms
Select row ~4 ms ~12 ms ~5 ms ~2 ms ~1 ms ~1 ms server N/A <1 ms
Swap rows ~4 ms ~15 ms ~7 ms ~3 ms ~1 ms ~2 ms server N/A <1 ms
Remove row ~4 ms ~14 ms ~6 ms ~2 ms ~1 ms ~2 ms server N/A <1 ms
Clear all rows ~1.4 ms ~8 ms ~4 ms ~3 ms ~1 ms ~2 ms server N/A <1 ms
Create 10,000 rows ~210 ms ~520 ms ~360 ms ~150 ms ~65 ms ~95 ms server N/A ~40 ms
Startup time ~3 ms ~8 ms ~5 ms ~20 ms ~2 ms ~3 ms ~6 ms ~5 ms 0 ms
Memory
Ready memory (baseline) ~1.2 MB ~1.8 MB ~1.4 MB ~3.5 MB ~0.8 MB ~1.0 MB ~1.5 MB ~1.3 MB ~0.5 MB
After 1K rows ~5 MB ~8 MB ~6 MB ~7 MB ~3 MB ~3.5 MB varies N/A ~2.5 MB
DX & Architecture
Reactivity model Manual xx() Proxy Proxy Proxy Compiled (runes) Lit reactive props Server-driven Controller-based Manual
Approach Inline directives Inline directives Inline directives SFC / templates Compiled SFC Web Components HTML attributes Data controllers Imperative
TypeScript support No types Plugin No Full Full Full No Full N/A
DevTools extension No Yes No Yes Yes Chrome Extension No N/A
Plugin / extension system No Yes No Yes Actions Decorators Yes No N/A
Router built-in No No No Vue Router SvelteKit No hx-boost Turbo N/A
Keyed list diffing Yes Yes Yes Yes Yes Yes server N/A DIY
Async / Suspense xx-await No No Suspense await blocks DIY hx-indicator No DIY
SSR / pre-rendering Yes (jsdom) No No Nuxt SvelteKit SSR built-in Server-first Rails SSR DIY
CSP compatible No Yes (v3) No Yes Yes Yes Yes Yes Yes
Source LOC ~1,373 ~5,000 ~2,000 ~40,000 compiler ~6,000 ~3,500 ~2,000 N/A
Ecosystem & Community
npm weekly downloads New ~350K ~15K ~4.5M ~1.2M ~300K ~200K ~100K N/A
Learning curve ~1 hour ~1 hour ~1 hour ~1 week ~2 days ~2 days ~1 hour ~2 days Already known
Hiring / job market None Niche None Strong Growing Niche Niche Rails Universal

* Svelte compiles away at build time; runtime size varies by app. HTMX and Stimulus delegate rendering to the server, so client-side DOM benchmarks don't directly apply. Numbers for other frameworks are approximate ranges from js-framework-benchmark (keyed, Chrome 120+). xxdom.js numbers are measured on this machinerun them yourself.

▶ Run the Full Benchmark Suite in Your Browser

Bundle Size Visual

Minified + gzipped transfer sizes. How much JavaScript are you shipping?

xxdom.js
5.2 KB
Lit
~5.8 KB
Petite-Vue
~6.4 KB
Stimulus
~7 KB
Alpine.js
~14.4 KB
HTMX
~14 KB
Vue 3
~33 KB

FAQ

This entire accordion is built with xx-for over a data array + xx-if to toggle answers.

<script> var faqItems = [ { q: 'How does xxdom differ?', a: '...', open: false }, // ... more items ]; function toggleFaq(item) { item.open = !item.open; xx(); } </script> <div xx-for="item of faqItems"> <button onclick="toggleFaq(this.$scope.item)"> <span xx-text="item.q"></span> </button> <div xx-if="item.open" xx-text="item.a"></div> </div>

Directive-by-Directive Comparison

Every feature that lightweight inline-directive frameworks offer, mapped across the landscape. This is the table you wish existed when choosing a framework.

Feature / Directive xxdom.js Alpine.js Petite-Vue Vue 3 Svelte 5 Lit HTMX
Templating & Rendering
Text interpolation {{ "{" + "{}" + "}" }} Lit html
Text binding x-text xx-text x-text v-text v-text implicit implicit
HTML binding x-html xx-html x-html v-html v-html {@html} unsafeHTML hx-swap
Show / hide x-show x-show v-show v-show CSS class CSS class
Conditional x-if xx-if x-if v-if v-if {#if} JS in template
Else / else-if v-else v-else-if {:else} ternary
Loop x-for xx-for x-for v-for v-for {#each} repeat()
Keyed iterations xx-for :key :key :key :key (item.id) key fn server
Data & Reactivity
Scoped state x-data xx-scope x-data v-scope data() $state properties
Two-way binding x-model xx-model x-model v-model v-model bind: manual
Computed / derived values JS expression getter in data computed() computed() $derived getter
Watchers / effects x-effect v-effect watch() $effect updated()
Global store global vars Alpine.store shared obj Pinia stores context
Events & Attribute Binding
Event binding x-on xx-on x-on / @ v-on / @ v-on / @ on: @event hx-trigger
Event modifiers (.prevent, .stop) .prevent .stop .prevent .stop .self .once .prevent .stop full set |preventDefault limited
Key modifiers (.enter, .escape) .enter .esc etc .enter .escape etc .enter .esc full set manual
Attribute binding x-bind xx-attr x-bind / : v-bind / : v-bind / : implicit implicit hx-vals
Class binding (object syntax) xx-class x-bind:class :class :class class: classMap()
Style binding (object syntax) xx-style x-bind:style :style :style style: styleMap()
Property binding xx-prop x-bind v-bind .prop implicit .prop
Components & Lifecycle
Components / templates xx-component Alpine.data functions SFC SFC LitElement
Slots / content projection <slot> <slot> <slot>
Init hook xx-init x-init @mounted onMounted onMount connectedCallback
Mounted hook xx-mounted x-init (same) @mounted onMounted onMount firstUpdated
Updated hook xx-updated onUpdated afterUpdate updated()
Destroy hook xx-destroy x-destroy (Alpine.js 3.14+) onUnmounted onDestroy disconnectedCallback
Advanced Features
Async / Suspense xx-await Suspense {#await} until() hx-indicator
Transitions / animations x-transition Transition transition: CSS only hx-swap transition
Element ref $el in scope x-ref / $refs $refs ref() bind:this @query
Teleport / portal x-teleport Teleport portal action hx-target
Cloak (hide until ready) x-cloak v-cloak v-cloak compiled shadow DOM
Ignore subtree x-ignore v-pre N/A N/A
Dynamic template selection xx-tmpl :is svelte:component manual
Render batching xx.render() $nextTick $nextTick nextTick tick() updateComplete N/A

= Supported    ~ = Partial / workaround    = Not available    This matrix covers the inline-directive landscape most relevant to xxdom.js. React, Angular, and Solid use fundamentally different paradigms (JSX/templates) and are not directly comparable directive-by-directive.

What Developers Are Asking For

Recurring themes from Reddit, GitHub discussions, and framework comparison threads. Here's how xxdom.js addresses (or honestly doesn't address) each one.

📦

"I just want to add interactivity to my HTML without a build step"

The #1 most common request. Developers working with static sites, WordPress, Django, Rails, or PHP templates want to sprinkle in reactivity without npm, webpack, or Vite. xxdom.js was built exactly for this: one <script> tag and you're done.

xxdom solves this

"Alpine.js is great but too big / too magical"

Many developers love Alpine's DX but find its 14KB payload and proxy-based reactivity harder to debug than expected. xxdom.js is 3x smaller and uses explicit xx() calls — no proxy magic, no hidden re-renders, no stale closure surprises.

xxdom solves this
🔎

"I want to read and understand the entire source"

A surprising number of developers want to fully understand their dependencies. xxdom.js is 1,373 lines of well-structured JavaScript — readable in a single sitting. Try doing that with Vue (40K LOC) or even Alpine (5K LOC).

xxdom solves this
🔧

"I need x-show for simple toggle visibility"

Many developers reach for x-show (toggle display via CSS) as a lighter alternative to x-if (DOM insertion/removal). xxdom.js currently lacks this — you can use xx-style="{display: show ? '' : 'none'}" as a workaround.

planned
🎨

"Transitions and animations on enter/leave"

Alpine's x-transition and Vue's <Transition> are highly requested features. xxdom.js doesn't have built-in transitions yet. You can use CSS transitions on elements, but there's no automatic enter/leave animation support.

planned
🔒

"CSP compliance is a hard requirement"

Enterprise and government sites often require strict Content Security Policy. xxdom.js uses Function() for expression evaluation, which requires unsafe-eval. This is a fundamental design trade-off for inline expression convenience.

by design
🚀

"I want TypeScript support and type checking"

TypeScript is the #1 requested DX feature across all frameworks. xxdom.js doesn't ship type definitions yet. Since expressions are evaluated at runtime via Function(), full type checking of inline expressions isn't feasible — but type defs for the API are possible.

planned (API types)
💡

"x-else and x-else-if would save so much boilerplate"

Currently you need separate xx-if blocks with inverted conditions. An xx-else directive would reduce duplication and improve readability. This is actively being considered.

planned
📋

"Automatic reactivity without calling xx() would be nice"

This is the most polarizing request. Proxy-based auto-reactivity (like Alpine/Vue) eliminates manual xx() calls but adds complexity, can cause unexpected re-renders, and makes debugging harder. xxdom chose explicit rendering deliberately — it's a feature, not a limitation.

by design

When to use xxdom.js

Perfect For

Rapid prototyping without any toolchain
Adding interactivity to static HTML pages
Embedded widgets and micro-frontends
Learning how DOM frameworks work internally
Codepen/JSFiddle demos and experiments
Environments where npm/bundlers aren't available

Consider Alternatives For

Large single-page applications (use Vue, React, Svelte)
Strict CSP environments (use Alpine.js)
SEO-critical server-rendered apps (use Next.js, Nuxt)
Enterprise apps needing an ecosystem (use Angular)

Up and running in 60 seconds

1

Add the script tag

One line. That's the entire "install".

<script src="https://unpkg.com/xxdom/dist/xx.min.js"></script>
2

Write your template

Use directives directly in your HTML. No compilation needed.

3

Open in any browser

No dev server needed. Double-click the HTML file. It just works.

Or install via npm

npm install xxdom // CommonJS const xx = require('xxdom'); // ES Modules import xx from 'xxdom/dist/xx.esm.js';

Join the Community

xxdom.js is open-source and welcoming contributors. Whether you find a bug, add a feature, or just star the repo — every bit helps.

Ways you can help:

Report bugs Write tests Add xx-else directive Add xx-show directive Improve error messages Create examples Write tutorials Spread the word