I've built Grains.js. Grains.js is a lightweight framework that brings reactivity and state management to vanilla HTML, similar to Alpine.js and HTMX, but with built-in state tracking. Grains.js uses directives as a declarative approach and pure functions for state changers.

Getting Started

First, you need to include Grains.js into your page:

<span class="na">src="https://mk0y.github.io/grains.js/dist/grains.min.js"
>

To define state in Grains.js, you use the g-state directive. This automatically initializes a reactive state that can be referenced throughout your HTML.

Example: Counter with Reactive State

g-state="counter" g-init='{"count": 0}'>
  Count:  g-text="count">
   g-on:click="increment">Increment
   g-on:click="decrement">Decrement

How It Works

  1. Define state: The g-state="counter" initializes a state variable called counter. This creates a global variable counter. If you inspect it in console as window.counter you'll see its value as Proxy(Object) {count: 0}. Whereas window.counter.count is equal to zero.

  2. Set initial value: The g-init='{"count": 0}' assigns an initial value. This can also be like g-init="myCount", which is referenced to a global variable window.myCount that you have to create.

  3. Bind state to elements: The updates dynamically when counter changes.

  4. Modify state with events: The g-on:click="increment" updates the state on button click.

Define State Changers

State changes are immutable and to dispatch the change we only create global pure functions. In our case increment is a global pure function, which you can inspect as window.increment.

To dispatch the change we use .set method and send the new state object as an argument:

function increment(ctx) {
    ctx.set({ count: ctx.get("count") + 1 });
  }
  function decrement(ctx) {
    ctx.set({ count: ctx.get("count") - 1 });
  }

Take a look at minimal example to get acquainted: https://mk0y.github.io/grains.js/examples/minimal.html.

All examples can be found in the repo: https://github.com/mk0y/grains.js/tree/main/examples.

Why Use Grains.js?

✅ Lightweight & Fast
✅ Declarative State Management
✅ Zero Build Step
✅ Reactive Without Virtual DOM

With Grains.js, managing state in HTML is intuitive and powerful, making it a great alternative for small, interactive UIs without the overhead of larger frameworks.

🚀 Try it out and bring reactivity to your HTML effortlessly!