minim

Reactive HTML in 1.2 KB.

Minim binds the HTML your server already rendered to a JSON state tag. No build step, no dependencies, nothing eval'd, not one line of JavaScript on the page โ€” and it is the smallest library that does this.

Paste the 1.2 KB snippet See it work

Minified + gzipped

1.2 KBMinim lite โ€” every binding on this page

  1. Minim lite1,260 B
  2. petite-vue7,080 B ยท 5.6ร—
  3. Alpine.js19,911 B ยท 16ร—

Reactive core only

  1. Minim516 B
  2. usignal882 B ยท 1.7ร—
  3. VanJS1,153 B ยท 2.2ร—
  4. hyperactiv1,220 B ยท 2.4ร—

Size matters.

Measured the same way for everyone: the latest npm source, terser, gzip -9. Minim's reactive core is the smallest on the chart, and the paste-in build with every binding on this page is a fifth the size of petite-vue and a sixteenth of Alpine.

Reactive core state + effects, min+gzip Minim core 516 B usignal 882 B ยท 1.7ร— Minim VanJS 1,153 B ยท 2.2ร— Minim hyperactiv 1,220 B ยท 2.4ร— Minim nanostores 2,339 B ยท 4.5ร— Minim HTML-first, no JavaScript on the page bind data-* attributes, min+gzip Minim lite (everything) 1,260 B petite-vue 7,080 B ยท 5.6ร— Minim Alpine.js 19,911 B ยท 16ร— Minim
Bytes, minified and gzipped. The full table with brotli numbers is in the README.

It binds by key, not by expression.

An input with data-model writes to the state; anything with data-watch reads from it; data-bind mirrors values into attributes; a <button command="--inc"> changes it with no handler at all. Keys can be dotted paths. There is no expression language and nothing is evaluated, which is why Minim runs under a strict Content-Security-Policy. The panel on the right is the state of this page, live.

Change the state

Or press a button โ€” no handlers, no onclick, just command

What the page sees

{ "name": "", "showName": false, "text": "fruit salad", "favorites": "๐Ÿฅฅ ๐Ÿ“ ๐Ÿ", "number": 22, "smallest": 11, "largest": 23, "count": 0, "user": { "city": "Punta Gorda" }, "todos": [ 2 items ], "level": 40, "accent": "#1a7a37", "spin": false }
<script type="application/json" data-state="app">{ "name": "", "count": 0, "showName": false }</script>

<input data-model="name">
<p data-show="showName" hidden>Hello, <b data-watch="name"></b></p>
<button type="button" commandfor="root" command="--inc" value="count">count +1</button>

Buttons without JavaScript.

Plain <button command="--inc" commandfor="root"> elements. The browser's Invoker Commands API delivers the click as a command event and Minim applies it to the state โ€” the same key the panel above shows. Nothing to wire.

0

clicks, and 2 todos

Todos

A list, animated, still no JavaScript.

Type and press Enter: that's data-command="keydown.Enter:--push" on the input. Each item is stamped from a <template data-each="todos"> where $ is the item, $.done one of its fields and $i its index.

Adding and removing animates through the View Transitions API where the browser has it (an-vt), and with @starting-style everywhere else (an-in on each item). The input and the "add a todo" button carry data-as="text", so what they push is an object like the two items the page started with.

State drives CSS, and CSS does the animation.

data-bind also writes CSS custom properties, and data-class toggles classes. The transitions and keyframes live in the stylesheet โ€” here they are sscss's motion utilities, an-grow, an-spin, an-tone โ€” and state only flips the switch. This section's accent, the bar and the spinning mark are all driven by the keys shown in the panel at the top of the page.

minim

A native dialog, the same state.

The browser's own command="show-modal" opens a <dialog>. The fields inside bind to the same keys as everything else, so the greeting and the panel at the top update behind the modal while you type.

Profile

Markup that arrives later binds itself.

A fetch response, an htmx swap, a PHP partial โ€” anything inserted after load is picked up by a MutationObserver and bound. Removed markup lets go of its bindings. The only script written for this page is the three lines that fake the fragment below.

Use it.

Paste the lite build into <head> and your page makes no request for a script at all, which is how this page scores what it scores. Or load the module build, which adds lists, classes, CSS variables and element commands.

<!-- paste-in: 1.2 KB, the snippet is in lite/README.md -->
<script>/* minim.min.js */</script>

<!-- or the module build, auto-boots -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@labs_kai/minim@3/dist/index.js"></script>

<!-- npm -->
npm i @labs_kai/minim

What a page needs

A JSON state tag. Attributes that name keys: data-model, data-watch, data-show, data-bind, data-class, data-each, data-command. Buttons with command. That's the whole API.

And for the look

This page is styled with sscss: short utility classes, generated into one <style> with only the rules used, dark mode and the motion utilities included. Minim lite and the sscss core are 1.6 KB together.

Read the README sscss