849 bytes brotli
The engine
AI writes to.
Frameworks were designed for human teams. mx is the rendering engine that fits inside a context window - zero dependencies, zero build step, direct DOM reconciliation.
Scroll
The next million apps won't be scaffolded with npm install. They'll be prompted into existence - single files, generated and discarded. The engine that powers them should be small enough to fit in a system prompt.
Why frameworks fail AI
01
Token tax
Every import statement, every JSX transpilation hint, every config file is tokens the model reasons about but the user never sees. Framework ceremony is inference cost.
02
Config gravity
Minification is real. But webpack.config.js, tsconfig.json, babel presets, vite plugins - AI misconfigures these more than it misconfigures code. mx works as a raw script tag. You can still minify, but you never have to configure.
03
API surface
useEffect, useMemo, useCallback, forwardRef, Suspense, Server Components... Every API is a hallucination vector. mx exposes three globals: mx, dom, define.
04
Weight mismatch
React 19 + ReactDOM is 272KB minified. mx is under 2KB. Less surface area means less to misuse, less to hallucinate, less to debug when AI gets it wrong.
The entire API
mx - Lightweight nodes
Describe what you want.
// Array descriptions. No JSX, no build step.
mx.div({ class: "card" },
mx.h2("Hello"),
mx.p("World")
)dom - Real elements
Build it now. Mount it anywhere.
// Same syntax. Returns real DOM nodes.
let el = dom.button(
{ onclick: () => alert("shipped") },
"Deploy"
)
document.body.append(el)define - Components
State, reactivity, re-rendering. Done.
define("counter", {
$({ count = 0 }) {
return mx.div(
mx.button({
onclick: () => this.$({ count: count - 1 })
}, "−"),
mx.span(count),
mx.button({
onclick: () => this.$({ count: count + 1 })
}, "+")
)
}
})
// Render it
document.getElementById("app").render(mx.counter())Framework comparison
| Metric | React 19 | Vue 3 | Preact | mx |
|---|
| Minified | 272,028 | 105,401 | 14,826 | 2,001 |
| Gzip | 67,982 | 39,867 | 6,087 | 996 |
| Brotli | 56,225 | 35,788 | 5,514 | 849 |
| Build step | Yes (JSX) | Yes (SFC) | Yes (JSX) | No |
| npm packages | react + react-dom | vue | preact | 0 |
| Component system | Yes | Yes | Yes | Yes |
| Fits in system prompt | No (272KB) | No (105KB) | No (15KB) | Yes (2KB) |
Live on this page
These demos are live - inspect the page or switch to the source tabs to see how they're built.
1
Person 1
Engineer
New York
2
Person 2
Designer
Los Angeles
3
Person 3
PM
Chicago
4
Person 4
Data Scientist
Austin
5
Person 5
DevOps
Seattle
6
Person 6
Engineer
New York
7
Person 7
Designer
Los Angeles
8
Person 8
PM
Chicago
9
Person 9
Data Scientist
Austin
April 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|
| | | 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
// ── Virtual scroll container ──
define('virtual-container', {
$({ height = 400, rowHeight = 48, rows = [] }) {
this.$visibleRows = Math.floor((height - 1) / rowHeight)
this.$lastOffset = 0
this.style.height = height + 'px'
let renderVisible = offset => {
let visible = rows.slice(offset, offset + this.$visibleRows + 1)
return [
mx.div({ style: `height:${offset * rowHeight}px` }),
...visible,
mx.div({ style: `height:${(rows.length - offset - visible.length) * rowHeight}px` })
]
}
this.onscroll = e => {
let scrollTop = Math.max(0, e.target.scrollTop)
let offset = Math.floor(scrollTop / rowHeight)
if (offset === this.$lastOffset) return
this.$lastOffset = offset
this.render(...renderVisible(offset))
this.scrollTop = scrollTop
}
return renderVisible(0)
}
})
// ── Table row ──
define('a-row', {
$({ columns, data }) {
return columns.map(col =>
mx.div({ class: 'table-cell' },
col.fn?.(data[col.key], data) || data[col.key]
)
)
}
})
// ── 10,000 rows, virtualized ──
let cols = [
{ key: 'name', label: 'Name' },
{ key: 'role', label: 'Role' },
{ key: 'city', label: 'City' },
{ key: 'id', label: '#', fn: v => v }
]
let data = Array.from({ length: 10000 }, (_, i) => ({
name: `Person ${i + 1}`,
role: ['Engineer','Designer','PM','Data','DevOps'][i % 5],
city: ['NYC','LA','Chicago','Austin','Seattle'][i % 5],
id: i + 1
}))
let rows = data.map(d => mx.aRow({ columns: cols, data: d }))
let header = mx.div({ class: 'table-header' },
...cols.map(c => mx.div({ class: 'table-cell' }, c.label))
)
demo.render(header, mx.vC({ height: 400, rowHeight: 48, rows }))// ── Date picker ──
let selectedDate = new Date()
function renderDatePicker() {
let year = selectedDate.getFullYear()
let month = selectedDate.getMonth()
let daysInMonth = new Date(year, month + 1, 0).getDate()
let firstDay = new Date(year, month, 1).getDay()
let days = []
for (let i = 0; i < firstDay; i++) days.push('')
for (let d = 1; d <= daysInMonth; d++) days.push(d)
let weeks = []
for (let w = 0; w < Math.ceil(days.length / 7); w++) {
let cells = days.slice(w * 7, (w + 1) * 7).map(day => {
let cls = !day ? 'empty' : day === selectedDate.getDate() ? 'selected' : ''
return mx.td({ class: cls, onclick() {
if (day) { selectedDate.setDate(day); renderDatePicker() }
}}, day || '')
})
weeks.push(mx.tr(...cells))
}
host.render(
mx.div({ class: 'dp' },
mx.div({ class: 'dp-nav' },
mx.button({ onclick() { selectedDate.setMonth(month - 1); renderDatePicker() } }, '←'),
mx.span({ class: 'dp-title' },
new Date(year, month).toLocaleString('default', { month: 'long' }) + ' ' + year
),
mx.button({ onclick() { selectedDate.setMonth(month + 1); renderDatePicker() } }, '→')
),
mx.table(
mx.thead(mx.tr(
mx.th('Su'), mx.th('Mo'), mx.th('Tu'), mx.th('We'),
mx.th('Th'), mx.th('Fr'), mx.th('Sa')
)),
mx.tbody(...weeks)
)
)
)
}
renderDatePicker()Give AI an engine
that fits.
849 bytes brotli. Zero dependencies. The entire engine fits inside a system prompt - not as a reference, but as source code. The app is the prompt. The engine is mx.
mx - 849 bytes brotli. Zero dependencies. Zero build steps.
ENGINE: 849B BROTLI