hctx — The Missing Client-Side Layer for HTMX
I’ve been building with HTMX for a while now, and I genuinely love the model:
Server-driven UI
HTML over the wire
Minimal JavaScript
No heavy frontend build pipeline
It feels clean. Predictable. Refreshing.
But eventually, I kept running into the same problem.
Not a big one.
Just… small things.
A counter
A toggle
Client-side validation before submit
A temporary UI state
A small interactive widget
Not enough to justify React.
Too structured for random vanilla JS event listeners scattered everywhere.
So I built hctx.
The Problem: “Just a Little State”
HTMX intentionally avoids managing client-side state. That’s part of its philosophy.
But real applications often need some client logic.
And when that happens, you usually end up with one of these:
Inline
<script>blocks with DOM queries and event listenersGlobal variables floating around
A growing mess of tiny utilities
Or… pulling in a full framework just to feel organized
There’s a huge gap between:
“I just need a sprinkle of JS”
and
“Fine, I’ll install a full SPA framework.”
hctx lives in that gap.
What is hctx?
hctx is a tiny (~5KB gzipped) declarative layer that adds structured client-side behavior directly through HTML attributes.
It’s designed specifically to sit alongside HTMX.
The core idea is simple:
Actions mutate state
Effects update the DOM
HTML wires everything together
That’s it.
A Simple Example
Here’s a counter:
<div hctx="counter">
<span hc-effect="render on hc:statechanged">0</span>
<button hc-action="increment on click">+1</button>
<button hc-action="decrement on click">-1</button>
</div>
<script>
hctx.newCtx("counter", () => ({
data: { count: 0 },
actions: {
increment: ({ data }) => { data.count++; },
decrement: ({ data }) => { data.count--; }
},
effects: {
render: {
handle: ({ data, el }) => { el.textContent = data.count; },
subscribe: ({ add, data }) => { add(data, "count"); }
}
}
}));
</script>
What’s happening?
dataholds state.actionsmutate it.effectsreact to changes and update the DOM.HTML binds user interactions declaratively:
hc-action="increment on click"
hc-effect="render on hc:statechanged"
No component files.
No build tools required.
No runtime overhead beyond ~5KB.
Why Not Just Use Vanilla JS?
You can.
But once you have more than one interactive widget, you’ll notice patterns emerging:
State needs structure.
DOM updates need to be reactive.
Cleanup becomes important.
Cross-component communication appears.
Async logic creeps in.
hctx gives you:
A predictable structure
Explicit separation between mutation and DOM updates
Scoped contexts
Automatic cleanup
Optional middleware and stores
Without turning your project into a framework-driven architecture.
Designed for HTMX
HTMX swaps fragments into the DOM.
That means your client logic must handle dynamic insertion.
hctx uses a MutationObserver to automatically wire newly inserted elements.
So when HTMX swaps HTML into the page, hctx just works.
No manual re-initialization.
No lifecycle hacks.
Just declarative behavior.
Features at a Glance
~5KB gzipped
Zero dependencies
Works via
<script>tag (IIFE) or ES moduleFull TypeScript support
Typed contexts
Typed stores
Typed middleware
Async actions
Cross-context communication
Fragments (same context across multiple DOM regions)
Write traps enforcing:
Actions mutate
Effects read
It scales when you need it to — but doesn’t force you to use everything.
What hctx Is Not
It’s not React.
It’s not a SPA framework.
It’s not trying to replace HTMX.
It’s for when you want:
Server-driven rendering
Minimal JS
But still need structured client state
Think of it as:
Alpine.js-level simplicity
A stricter action/effect mental model
Explicit state discipline
Designed for HTMX from day one
When Should You Use It?
Use hctx when:
You’re building HTMX apps
You want client-side behavior without framework overhead
You care about bundle size
You prefer HTML-first architecture
You want structure without complexity
Don’t use it if:
You’re building a complex SPA with client routing
You need full client-side rendering
You want a large ecosystem of UI components
Different tools for different jobs.
Installation
npm
npm install hctx
CDN
<script src="https://unpkg.com/hctx"></script>
Repo & docs:
https://github.com/aggroot/hctx
Why I Built It
I didn’t want to choose between:
Messy vanilla JS
orHeavy frameworks
I wanted something tiny, intentional, and disciplined.
Something that keeps the HTMX philosophy intact.
hctx is my attempt at that.
It’s still early (v0.1.0), and I’m actively refining it.
If you’re building HTMX apps and feel the same friction, I’d genuinely love your feedback.
What would make this useful for you?