The power of a modern framework, in a runtime the browser barely notices.
Your server keeps rendering HTML. sigmx fetches it, morphs it into the page without losing your place, and adds signals, two-way binding and streaming. The build ships only the plugins your markup uses. Zero dependencies.
Signals update in place. The second button asks this site's server for HTML.
On the wire
GET /api/time
Accept: text/event-stream, text/html, application/json200 OK
Content-Type: text/html…
Same id as the page, so it is morphed in. JSON would merge into signals. An event stream keeps delivering.
Every kilobyte, counted.
One tile is 512 bytes, brotli-compressed the way a CDN serves it. The core is the dark tiles; everything after it is plugins, and you only ship the ones you register. All 50 of them together, including the Alpine and htmx extensions, come to 21% less than htmx alone and 2% smaller than Datastar.
sigmx coresignals, store, expressions, the engine4.5 KB
sigmx, everythingall 50 plugins in one file11.5 KB
Datastar 1.0.3datastar.js11.8 KB
htmx 2.0.10htmx.min.js, core only14.6 KB
Alpine.js 3.17.1cdn.min.js, core only17.6 KB
corepluginsother libraries, measured the same way
Measured 2026-09-06: htmx 2.0.10 (htmx.min.js, core only), Datastar 1.0.3 (datastar.js), Alpine.js 3.17.1 (cdn.min.js, core only), with npm run size and npm run compare at brotli quality 11. Per-plugin figures and every preset are in bundle sizes.
One attribute, read left to right.
Everything the runtime does is declared in markup, and every part of an attribute means something. The browser and the build-time scanner read the same six pieces.
data- prefixThe attribute prefix. Configurable per project; hx- and x- style prefixes work too.
2
on pluginThe plugin that owns this attribute, 472 bytes, registered because this line uses it.
3
:input keyThe key: here an event name. For data-bind it names a signal, for data-class a class.
4
__debounce.300ms modifiersModifiers with arguments: debounce, throttle, prevent, once, window, outside and more.
5
@get actionAn action from a plugin. The HTML that comes back is morphed into the page by id.
6
$q signalA signal, two-way bound to the input by data-bind and read like any variable.
You write the markup. The scanner writes the bundle.
Auto mode reads your source the way a CSS framework scans for class names. Every attribute and action it finds drops its plugin into the bundle, along with the plugins that one needs; the other42 never leave the bundler. Nothing to maintain, nothing to forget, nothing shipped by accident.
8.2 KB brotli, measured for exactly these 8 plugins
data-signals142 B, line 1
data-on472 B, line 2
@post1592 B, line 2
data-text117 B, line 4
data-show135 B, line 5
@get1626 B, line 6
patch-elements1345 B, needed by @get
patch-signals67 B, needed by @get
core4.5 KB, signals, store, expressions, the engine
42 plugins never entered the file.
Brick widths are each plugin measured on its own; together they compress to the total on top. The same scanner runs as a CLI for any build, a Vite plugin for any framework, an Astro integration, and inside the Rust crate for projects without Node. Turn on precompilation as well and every expression becomes a real function in a table, so the runtime compiler leaves the bundle and a strict Content-Security-Policy needs no exceptions. How auto mode works.
Every built-in is twenty lines you could have written.
Three helpers make every directive, function and server-event handler in the library, and they are the same three you get. Nothing registers on import, so your plugins tree-shake, teardown is automatic, and auto mode scans them like the built-ins.
sigmx is not a new idea. It is a small, independent implementation of ideas three libraries proved first, and it would not exist without them. If you already use one, you will feel at home.
Showed that the browser already is a hypermedia client, and only needed a few more verbs. Any element can make a request, the server answers with HTML, and the page swaps it in. Server-sent events carry updates.
Showed that state and behaviour belong next to the markup they drive. x-data, x-model and x-show made reactive attributes feel like HTML, with expressions that are simply JavaScript.
Showed that the two ideas are one. Signals in the browser and patches from the server share a single attribute vocabulary, and the server can drive the page as easily as the page drives itself.
what sigmx keeps
the attribute vocabulary
signals and server patches together
markup written for it runs unchanged
What is different is the implementation: no shared code, a smaller and simpler codebase, plugins you register rather than side effects that register themselves, and a build that does the heavy lifting. The introduction tells the full story.