PraxisJS
npm create praxisjs@latest

Signal-driven frontend framework.

Class components with TC39 decorators. Fine-grained signals update only the exact DOM nodes they're bound to — no virtual DOM, no diffing.

Design principles

The ideas behind every decision.

Render once, update precisely

render() runs exactly once on mount. Every subsequent update targets only the DOM nodes that subscribed to a changed signal — nothing more.

Reactivity you can read

@State, @Watch and @Computed sit directly on class fields. Every reactive dependency is a decorator you can see, rename, or delete — no implicit tracking.

No reconciler, no diff

There is no virtual DOM tree to compare. A signal change triggers a direct DOM write on the exact node bound to it. The rest of the page is untouched.

How it works

Three steps. No magic.

01Declare

Annotate fields with decorators

@State, @Prop, @Resource — each one wraps a class field in a reactive signal. No store setup, no context providers, no boilerplate.

@State() count = 0
02Bind

Wrap expressions in arrow functions

{() => this.count} subscribes that DOM node to the signal. Static reads like {this.count} are safe too — they snapshot the value at render time.

<p>{() => this.count}</p>
03Update

Only subscribed nodes re-evaluate

Assign a new value. The signal notifies its subscribers. Only those DOM nodes update. render() stays idle. No tree traversal, no component re-run.

this.count++

Quick start

Up and running in minutes.

The CLI scaffolds a complete project — TypeScript, Vite, JSX transform, decorator support, and HMR all pre-configured.

01Scaffold
$npm create praxisjs@latest

Creates a new project with TypeScript, Vite, JSX transform, and HMR pre-configured.

02Install
$cd my-app && npm install

PraxisJS has zero runtime dependencies. The install is fast.

03Run
$npm run dev

Vite starts the dev server. Open the browser and start editing your first component.