I just published a small JavaScript library called jsRibbon.
It’s not another SPA framework. It’s not trying to replace React. It’s not trying to be clever.
It exists because I got tired.
Tired of:
- Hydration complexity
- Virtual DOM diffing for simple dashboards
- Writing state twice
- Destroying server-rendered HTML just to recreate it in JavaScript
Most backend-heavy applications do not need a client-side rendering engine. They need small reactive behavior layered on top of server-rendered HTML.
That’s what jsRibbon does.
The Problem I Kept Running Into
I build backend-first systems. Laravel. Blade. Server-rendered dashboards. Admin panels. Internal tools.
Most of the UI already comes from the server.
Then we sprinkle JavaScript for:
- Counters
- Toggles
- Forms
- Conditional visibility
- Small interactive components
But modern frontend tools assume JavaScript owns rendering.
That means:
- The DOM becomes disposable
- Hydration becomes a reconciliation step
- State moves outside the markup
- Complexity increases fast
For a CRUD dashboard.
That always felt backwards.
The Core Idea
What if:
- The server renders the HTML.
- The DOM already contains meaningful state.
- JavaScript enhances what exists.
- We never destroy server markup.
That’s jsRibbon.
It treats the DOM as canonical. Not a rendering target. Not something to diff against.
State is derived from markup.
No Virtual DOM
Virtual DOM is brilliant for large, client-heavy apps.
But most backend systems don’t need a second representation of the UI in memory.
If I update a value:
Update the actual DOM node.
Not an abstract tree that later patches the DOM.
jsRibbon mutates real nodes directly. No shadow tree. No reconciliation engine.
Just controlled updates.
Inspired, But Selective
I didn’t invent anything new. I extracted the useful parts.
From Knockout.js:
- Declarative
data-bind - Observable-style thinking
From React:
- Component mental model
- State-driven UI updates
From HTMX:
- Respect for server-rendered HTML
- Progressive enhancement
And I left behind:
- Virtual DOM diffing
- JSX
- Compilation
- Template DSLs
- Replacing the backend
The goal wasn’t to be revolutionary. It was to be restrained.
MutationObserver Instead of Hydration Rituals
jsRibbon uses MutationObserver.
When new HTML appears in the DOM:
- Injected by HTMX
- Rendered dynamically
- Inserted manually
It auto-initializes components.
No manual re-scan. No re-mounting logic. No rehydration ceremony.
The DOM changes. The library reacts.
That’s it.
DOM as State
If the markup already contains:
<input value="John" data-bind="value:name">
<span data-bind="text:name"></span>
Then "John" becomes initial state.
No duplicate initialization in JavaScript.
No writing defaults in two places.
The DOM is not an output target. It is the starting point.
Why It Works Well with HTMX
HTMX handles server communication and partial swaps.
jsRibbon handles local reactivity.
HTMX replaces markup. jsRibbon activates it.
That separation feels clean:
- Server renders structure.
- HTMX updates sections.
- jsRibbon manages local state.
No SPA architecture. No build step. No complicated client runtime.
Who This Is For
- Laravel / Blade apps
- Backend-first dashboards
- Systems that don’t need full client routing
- Teams that want reactivity without rewriting everything
If your app is mostly server-rendered and needs controlled client behavior, jsRibbon fits naturally.
If you’re building a client-only, highly animated product with deep interaction graphs, this is not the right tool.
And that’s fine.
Why I Published It
Because I kept building this pattern internally.
At some point, I stopped pretending it was “just helper code” and treated it as a library.
It has:
- Component registry
- Proxy-based state
- Scoped resolution
- Foreach hydration
- Mutation loop protection
It’s small. It’s deterministic. It doesn’t try to own your architecture.
And that’s the point.
Closing Thought
Most apps don’t need more abstraction.
They need less friction between server-rendered HTML and small interactive behavior.
jsRibbon is an attempt to reduce that gap.
If it stays small and predictable, it succeeds.
If it turns into another framework trying to dominate everything, I’ve failed.
You can check it out here: