Why I built Micra.js

I remember when jQuery sat on almost every site. Before the frameworks, it was simply the easy way to reach the DOM: attach a handler, show something, hide something, fire a request. Back then that solved most of what you needed. Then the frontend got more complicated. Mostly for good reasons — apps grew, and React, Vue, and Svelte grew with them. But you don't always need that much.

Plenty of projects are still ones where the server already sends finished HTML and the client needs only a little reactivity: open a modal, bump a counter, filter a table, switch a tab. Standing up a full framework with a build step and routing for that is often more than you want. And writing it in plain JavaScript gets awkward fast once you have to track state and keep it in sync with the DOM.

I was looking for something that gives you reactivity while staying as light as possible and needing no separate build. I wanted to just drop a script onto existing server markup and have it start reacting to state changes.

The core idea

Micra makes the HTML that came from the server reactive. You describe behavior in a plain JavaScript object, and in the markup you use simple data- attributes and directives.

Here's how it looks:


Open dialog




Example dialog
Close




Behavior is a plain JS object — methods and state, with no separate view layer:

Micra.define("modal", {
state: {
isOpen: false,
},
open() {
this.state.isOpen = true;
},
close() {
this.state.isOpen = false;
}, // data-show toggles visibility on its own
});

Micra.start(); // find data-component elements on the page and mount them

Click the button — open() sets isOpen = true, and the block with data-show="isOpen" appears. A click on "Close" or on the backdrop calls close() and it goes away. No hunting for elements with querySelector, no poking at styles. Only the state changed; the library handled the rest.

That's the whole idea. You write ordinary HTML. No keeping the tree in your head, hunting nodes by selector, and reconciling them with state by hand. Classes stay what they always were — for styling. And data- attributes say what happens on a given piece of markup: data-component marks where a component lives, data-show says when to show it, @click says what to call on click. The behavior is described right in the markup, and the markup stays readable HTML.

The hardest part — stopping in time

When I started on Micra, the hardest part wasn't thinking of what to add — it was working out what not to add. I kept wanting to do it "properly": a router, deep reactivity, a component system, a little language inside the directives. Every time that urge came up, I went back to the original goal — a small library you can just drop onto existing server markup.

The ~7 KB ceiling turned out to be a useful discipline. As long as you stay under it, you physically can't turn the tool into yet another framework. So Micra deliberately has no:

Client-side routing

Deep reactivity with nested objects

Component system of its own

Complex DSL inside the attributes

All of that can be added later (or not at all). The point is not to bloat the library for the sake of "completeness."

Who Micra is for

Micra isn't a replacement for React, Vue, or even Alpine.js. It's a tool for the cases where you want reactivity but don't want to drag a heavy toolchain along.

It fits well for:

Admin panels and internal tools

Small SaaS

Sites where the main markup is generated on the server (Rails, Laravel, Django, WordPress, and so on)

Cases where you want to add interactivity quickly without a separate frontend app

If a project needs complex client state, client-side routing, and heavy logic — reach for a real framework. Micra isn't meant for that.

I'm not claiming I invented anything revolutionary. Micra grew out of a set of practices I used myself and just settled into a separate library. If you're more comfortable with htmx, Stimulus, Alpine, or something else — use whatever fits your work best.

Micra lives at micrajs.dev, sources under MIT.