A signal-based micro-framework. No virtual DOM, no compiler, no build-time magic. Just signals and tagged templates. Performance-first reactivity.
No compiler, no config files, no boilerplate. Just install, write, and go.
One package, zero runtime dependencies. Works with Vite, Webpack, or directly via ESM CDN.
A plain function returning html``
is all you need. No class, no decorator, no JSX transform.
Call mount()
once. Every signal update after this happens automatically — no re-render calls, no manual DOM updates.
tsconfig.json,
no vite.config.ts,
no babel.config.js
required — run it straight from the browser with an import map.
A complete UI framework that fits in a single import. No virtual DOM overhead, no compiler step, no configuration files.
Signals update only the exact DOM nodes that depend on changed data. No diffing, no reconciliation, no wasted renders.
Templates are standard JavaScript tagged template literals. No JSX transform, no SFC compiler, no build-time magic needed.
Router, forms, stores, dependency injection, portals, error boundaries, transitions — all built-in. One import, zero config.
Every API is fully typed from the ground up. Typed injection keys, typed store signals, typed route params — real type safety.
If you know Vue's provide/inject, React's hooks, or Solid's signals — you'll feel right at home. The best ideas, unified.
User-provided strings are inserted via textContent. URI components are encoded. Built-in security from day one.
These demos simulate how Nix.js signals, computed values, and effects work. Interact with them to see fine-grained reactivity.
Under the hood, Nix.js is a four-layer stack. Each layer does exactly one job — signal, compute, bind, render.
Reading a signal inside effect() or html`` automatically registers a
subscription. No .subscribe() calls, no decorator, no annotation needed.
Each reactive expression inside html`` compiles to exactly one effect(). When the
signal changes, that one effect updates that one text node or attribute — nothing else.
Before each re-run, an effect disposes its previous subscriptions and runs its cleanup function (if any). Unmounting a component tears down every effect it owns.
Setting a signal to the same value it already holds is a no-op. No downstream effects are triggered, no DOM work happens — not even a microtask.
Multiple signal writes inside batch() queue their effects until the batch ends. All
subscribers see a consistent snapshot, and the DOM updates exactly once.
Read a signal with untrack() to get its value without creating a subscription. Useful for
reading config or context inside an effect you don't want to re-trigger.
Clean, readable code that does exactly what you'd expect. No magic, no surprises.
Create reactive values with signal(),
derive with computed(),
and watch with effect().
Three primitives power the entire framework.
import { signal, computed, effect } from "@deijose/nix-js"; // Reactive state const count = signal(0); const doubled = computed(() => count.value * 2); // Auto-runs when count changes effect(() => { console.log(`Count: ${count.value}`); console.log(`Doubled: ${doubled.value}`); }); count.value = 5; // logs: Count: 5, Doubled: 10 // Batch multiple writes — effect runs once batch(() => { count.value = 10; count.update(n => n + 1); });
Function components for pages and display. Class components when you need lifecycle hooks. Both work seamlessly together.
// Function component — simple & clean function Counter(): NixTemplate { const count = signal(0); return html` <p>${() => count.value}</p> <button @click=${() => count.value++}> +1 </button> `; } // Class component — with lifecycle class Clock extends NixComponent { time = signal(new Date().toLocaleTimeString()); onMount() { const id = setInterval(() => { this.time.value = new Date() .toLocaleTimeString(); }, 1000); return () => clearInterval(id); } render() { return html`<span>${() => this.time.value}</span>`; } }
No extra package. History API router with dynamic params, nested routes, query strings, guards, and lazy loading — ready to go.
import { createRouter, RouterView, Link, lazy } from "@deijose/nix-js"; const router = createRouter([ { path: "/", component: () => HomePage() }, { path: "/about", component: () => AboutPage() }, { path: "/dashboard", component: () => new DashboardLayout(), children: [ { path: "/stats", component: lazy( () => import("./pages/Stats")) }, { path: "/settings", component: lazy( () => import("./pages/Settings")) }, ], }, { path: "*", component: () => NotFound() }, ]); // Auth guard — redirect if not logged in router.beforeEach((to, from) => { if (to.startsWith("/dashboard") && !isAuth()) return "/login"; });
Every property becomes a signal automatically. Add typed actions, reset to initial state with $reset(). No reducers, no dispatchers.
import { createStore } from "@deijose/nix-js"; const cart = createStore( { items: [] as string[], total: 0, }, (s) => ({ add(item: string) { s.items.update(arr => [...arr, item]); s.total.update(n => n + 1); }, remove(item: string) { s.items.update(arr => arr.filter(i => i !== item)); s.total.update(n => n - 1); }, clear() { cart.$reset(); }, }) ); cart.add("Milk"); cart.items.value; // ["Milk"] cart.total.value; // 1
Manage complex forms with ease. Built-in validation, dynamic field arrays, and enhanced state tracking for submission and dirty states.
import { createForm, useFieldArray, required, email } from "@deijose/nix-js"; const { form, handleSubmit } = createForm({ name: "", emails: ["test@example.com"] }, { validateOn: 'blur', validators: { name: [required()] } }); // Dynamic field array for emails const { fields, append, remove } = useFieldArray( form.emails, { validators: [required(), email()] } ); const onSubmit = handleSubmit(data => { console.log("Form Submitting...", data); });
Stop juggling third-party packages. Nix.js ships with every feature you need to build production applications.
Built-in field validation, dynamic arrays, and Zod/Valibot interop. Now includes useFieldArray for dynamic lists.
const form = createForm(
{ name: "", email: "" },
{ validators: {
name: [required(), minLength(2)],
email: [required(), email()],
}}
);
Render modals, tooltips, and toasts outside the component tree. Supports outlet tokens, refs, and provide/inject.
const modal = portal(
html`<div class="modal">
<h2>Confirm action</h2>
<button @click=${close}>OK</button>
</div>`
);
Catch render and reactive errors gracefully. Show fallback UIs without crashing the entire application.
createErrorBoundary(
new DataWidget(),
(err) => html`
<p class="error">
Failed: ${String(err)}
</p>`
);
CSS class-based enter/leave animations. No wrapper elements, JS hooks for full control, appear on first render.
transition(
() => show.value
? html`<p>Hello!</p>`
: null,
{ name: "fade", appear: true }
);
suspend() for data fetching, createQuery() for shared queries, lazy() for code-splitting. Smart invalidation without DOM rebuild.
suspend(
() => fetch("/api/users").then(r => r.json()),
(users) => html`
<ul>${users.map(u =>
html\`<li>${u.name}</li>\`
)}</ul>`,
{ invalidate: refreshKey }
);
Vue-style provide/inject with typed keys. Pass data down the tree without prop drilling. Nearest ancestor wins.
const THEME = createInjectionKey<
Signal<string>
>("theme");
provide(THEME, signal("dark"));
const theme = inject(THEME);
We measured Nix.js against the industry standards using the official 1,000-row stress tests. The result? Top-tier performance that rivals—and sometimes beats—hand-optimized Vanilla JS.
| Operation (1k rows) | Nix.js 1.3.0 | Nix.js 1.7.3 🚀 | Vanilla JS | Solid.js | Svelte 5 | Vue 3 | React 18 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| JS | Full | JS | Full | JS | Full | JS | Full | JS | Full | JS | Full | JS | Full | |
| Create rows Initial render | 220.2ms |
603.9ms |
20.3ms WIN
|
100.6ms |
~55ms |
~80ms |
~65ms |
~130ms |
~100ms |
~180ms |
~130ms |
~280ms |
~160ms |
~350ms |
| Replace rows Full array swap | 286.5ms |
567.5ms |
26.2ms WIN
|
110.8ms |
~55ms |
~85ms |
~70ms |
~140ms |
~105ms |
~190ms |
~135ms |
~290ms |
~165ms |
~360ms |
| Update (1 in 10) Fine-grained text update | 0.8ms |
40.1ms |
0.4ms TOP
|
31.3ms |
~4ms |
~15ms |
~5ms |
~20ms |
~8ms |
~30ms |
~12ms |
~45ms |
~15ms |
~55ms |
| Select row Highlight 1 element | 0.3ms |
21.6ms |
0.1ms TOP
|
23.2ms |
~2ms |
~8ms |
~3ms |
~12ms |
~5ms |
~18ms |
~8ms |
~28ms |
~10ms |
~35ms |
| Swap rows Swap index 2 and 998 | 53.3ms |
380.5ms |
15.6ms
|
93.0ms |
~5ms ★ |
~20ms |
~8ms |
~30ms |
~12ms |
~45ms |
~25ms |
~90ms |
~30ms |
~110ms |
| Clear rows Range.deleteContents() | 43.2ms |
307.5ms |
17.2ms WIN
|
33.1ms |
~30ms |
~50ms |
~35ms |
~60ms |
~45ms |
~75ms |
~80ms |
~150ms |
~95ms |
~180ms |
| Delete row Eliminar 1 fila | 1.9ms |
44.8ms |
0.9ms TOP
|
27.7ms |
~1ms |
~5ms |
~2ms |
~8ms |
~3ms |
~12ms |
~8ms |
~25ms |
~10ms |
~35ms |
| Gzipped Size Library footprint |
~10 KB
v1.3.0
|
~10 KB WIN
Router + Stores included
|
0 KB ★
Browser Native
|
~7 KB
Core only
|
~2 KB*
Requires compiler
|
~22 KB
Core + Runtime
|
~45 KB
React + DOM
|
|||||||
| Feature | Nix.js | React | Vue | Solid | Svelte |
|---|---|---|---|---|---|
| Router | Built-in ✓ | react-router | vue-router | @solidjs/router | svelte-kit |
| Form Validation | Built-in ✓ | react-hook-form | vee-validate | — | — |
| Global Stores | Built-in ✓ | zustand / redux | pinia | Built-in ✓ | svelte/store |
| Dependency Injection | Built-in ✓ | React Context | Built-in ✓ | createContext | getContext |
| Portals | Built-in ✓ | Built-in ✓ | Teleport ✓ | Built-in ✓ | — |
| Error Boundaries | Built-in ✓ | Built-in ✓ | errorHandler | Built-in ✓ | — |
| Transitions | Built-in ✓ | — | Built-in ✓ | — | Built-in ✓ |
Nix.js didn't emerge in a vacuum. It distills battle-tested ideas from the frameworks that shaped modern UI development — taking what works, discarding the overhead.
Lit pioneered the idea of using JavaScript's native tagged template literals to define HTML
templates — no compiler, no JSX, no virtual DOM. Nix.js adopts this exact approach: the html`` tag parses templates once and wires live
bindings directly to real DOM nodes.
Solid.js proved that signal-based fine-grained reactivity doesn't need a virtual DOM — just
wire effects directly to DOM nodes. Nix.js adopts the same reactive core: signal(), computed(), and effect() are the three primitives that power
everything.
Vue 3's Composition API introduced provide/inject, watch(), and typed lifecycle hooks as first-class
citizens. Nix.js mirrors this exactly: typed injection keys via createInjectionKey(), watch() with immediate/once options, and onMount / onUnmount hooks.
React proved that function components with colocated state are more composable than
class-only patterns. Nix.js supports both: function components (plain functions + html``, zero boilerplate) and class components
(NixComponent) only when lifecycle hooks are
needed.
Svelte's built-in transition: directive made
animations a first-class concern — without a separate animation library. Nix.js's transition() brings the same mental model: CSS
class-based enter/leave lifecycle with optional JS hooks and appear on first render.
MobX introduced transparent reactive tracking — read a value inside a reaction, and you're
automatically subscribed, no boilerplate. S.js formalized this into a dependency graph with batch() and untrack(). Nix.js inherits both: effects
auto-track their dependencies and untrack()
lets you opt out selectively.
The best frameworks aren't built from scratch — they're built on the shoulders of great ideas. Nix.js studies what works across the ecosystem and brings it together: tagged templates from Lit, fine-grained signals from Solid, provide/inject from Vue, function components from React, CSS transitions from Svelte, and transparent auto-tracking from MobX — unified into a single, zero-dependency, compiler-free package that respects your time and your bundle size.
Discover how the community is leveraging the Nix.js ecosystem to build high-performance UIs without the complexity.
A real-time cryptocurrency tracking dashboard demonstrating fine-grained reactivity, live data fetching, and dynamic UI updates.
Visit project
An academic tracking platform showcasing client-side routing, global state management, and nested layouts powered by Nix.js.
Visit project.js or even directly in a browser
<script type="module"> tag via an import map.
type="importmap" and https://esm.sh/@deijose/nix-js.
removeChild loops) which can trigger multiple internal browser reflows. Nix.js automates extreme low-level DOM optimizations for you. For instance, it uses Range.deleteContents() for atomic bulk clearing, and holds direct memory references to TextNodes for true O(1) updates. It writes the most optimal Vanilla JS possible, automatically.
Start building reactive UIs in minutes. No compiler, no config, no learning curve — just install and go.