htmx 4.0 "The fetch()ening": Inside the Core Rewrite — What Changed From 2.x, and How It Stacks Up Against Alpine.js
htmx 4.0 "The fetch()ening": Inside the Core Rewrite — What Changed From 2.x, and How It Stacks Up Against Alpine.js
On August 28, 2026, htmx shipped version 4.0.0 — the first major rewrite in the library's history, delivered by a project whose creator once promised there would never be a version three. The headline number is the same one that made htmx famous: a dependency-free library that fits in roughly 14–16 KB minified and gzipped, now rebuilt around the modern web platform. But the real story isn't the byte count. It's that the "anti-SPA" standard-bearer just rewrote its entire transport layer, quietly fixed its two most-maddening design quirks, and shipped the whole thing with a release discipline almost nobody else in the JavaScript ecosystem practices.

Why rewrite now? Blame a side project
The origin story is refreshingly honest. In "The fetch()ening," the November 2025 essay that announced htmx 4, creator Carson Gross admits he once declared "there would never be a version three of htmx" — then adds the loophole: "But, technically, I never said anything about a version four." To keep his word, version three was simply skipped. Version 4.0 it is.
The spark was fixi.js, a hyperminimalist hypermedia experiment Gross built on a whim. Working on it made him deeply familiar with fetch() and modern async JavaScript — and made the old implementation look dated. htmx had been riding XMLHttpRequest since version 1.0, a legacy of Internet Explorer support. Once the team (Gross, Michael West, Christian Tanul, and Alex Petros) started porting fixi plus the htmx test suite to fetch, something interesting happened: they "rediscovered why htmx did many of the things that it did" and migrated the new implementation back toward the old behavior. Eight months later, the behavioral differences between 2.x and 4.x are, per the announcement, "relatively small."
The philosophy is untouched: HTML over the wire, progressive enhancement, locality of behavior. Your server still renders fragments; htmx still swaps them into the DOM. What changed is everything under the hood — and three things on the surface.
4.0 vs 2.x: the three headline majors
1. Attribute inheritance is now explicit
This is the big one, and the team says so: in 2.x, attributes like hx-confirm and hx-target silently inherited from parent elements — a CSS-inspired idea Gross now calls his "biggest mistake in htmx 1.0 & 2.0," with results "roughly the same as CSS: powerful & maddening." A hx-confirm on a container could attach confirmation dialogs to descendants you forgot about; hx-disinherit existed purely to fight the feature.
In 4.0, inheritance only happens when you ask:
<!-- htmx 2: implicit inheritance -->
<div hx-confirm="Are you sure?">
<button hx-delete="/item/1">Delete</button>
</div>
<!-- htmx 4: explicit inheritance -->
<div hx-confirm:inherited="Are you sure?">
<button hx-delete="/item/1">Delete</button>
</div>
hx-inherit and hx-disinherit are removed — they're pointless now. A config flag (htmx.config.implicitInheritance = true) restores old behavior during migration.
2. Events got a real naming scheme
htmx 2's event names grew organically and were, charitably, inconsistent. All 4.0 events follow htmx:phase:action[:sub-action]:
| htmx 2.x | htmx 4 |
|---|---|
htmx:beforeRequest |
htmx:before:request |
htmx:afterRequest |
htmx:after:request |
htmx:beforeSwap |
htmx:before:swap |
htmx:afterSwap |
htmx:after:swap |
htmx:configRequest |
htmx:config:request |
Four separate error events (htmx:sendError, htmx:swapError, htmx:targetError, htmx:timeout) collapse into one htmx:error. The htmx:xhr:* events are gone — fetch has no equivalents — and htmx:validation:* events are replaced by native browser form validation.
3. The back button is a real request now
htmx 2 snapshotted the DOM into browser storage for history restoration. That cache captured mutations made by third-party scripts, so restoring could resurrect UI state whose JavaScript logic wasn't loaded — a chronic source of support tickets. In 4.0, localStorage history caching is gone entirely: back navigation re-fetches the page from the server and swaps it into <body> (or your [hx-history-elt]). Your back button now shows what the server says the page is. Need snapshots back? The opt-in hx-history-cache extension restores sessionStorage caching and is designed to integrate with Alpine.js.
The quieter breakers you'll actually hit
- Error responses swap by default. 2.x silently ignored 4xx/5xx bodies; 4.0 swaps everything except 204/304. Pair it with the new
hx-statusattribute to route status codes to targets:hx-status:422="target:#validation-errors". hx-disable→hx-ignore, andhx-disabled-elt→hx-disable. Rename in that order or you'll migrate one attribute into the other.- 60-second default timeout (2.x requests could hang forever).
hx-deleteno longer includes enclosing form data (fix withhx-include="closest form").- OOB swaps now happen after the main content swap, not before.
- Removed attributes:
hx-ext,hx-vars,hx-params,hx-prompt,hx-request,hx-history. Removed JS API helpers likehtmx.addClass()— use nativeclassList.
What you get for the breakage
The fetch() migration paid for itself with a feature haul:
- Morph swaps in core. Gross created the idiomorph DOM-morphing algorithm (adopted by Hotwire and others) years ago but excluded it from 2.x for size. With the complexity savings from fetch,
innerMorphandouterMorphare now built-in swap styles — no extension needed. <hx-partial>tags. Out-of-band swaps had grown "baroque" syntax for multi-target updates. Now a response can carry<hx-partial hx-target="#messages" hx-swap="beforeend">blocks that each behave like full htmx swaps — and plain OOB swaps go back to their simple id-replacement roots.- First-party streaming.
hx-sse,hx-ws, and a newhx-multipartstream HTML into the page — the direct payoff of fetch's readable streams, and the use case that originally inspired the rewrite. hx-queryissues the new HTTPQUERYmethod from RFC 10008 — safe reads with a body, first-class in a hypermedia library within months of the RFC landing.- hx-live, a new scripting layer "inspired by Alpine.js, jQuery and hyperscript" with what the team calls DOM-based, HATEOAS-friendly reactivity.
- htmax.js, a convenience bundle packaging htmx with the most popular extensions in one file.
- Plus
hx-preload,hx-download, and anupgrade-checkCLI (npx [email protected] upgrade-check -- ./templates) that scans your templates and prints every inheritance, event, and rename issue with file and line numbers — even flagging that your inherited CSRFhx-headerswon't reach child elements anymore. There are even official agent skill files for LLM-assisted migration.

The release discipline nobody else does
Here's the part worth stealing for your own projects: 4.0 is not tagged latest on npm. Because countless production sites load htmx from unversioned CDN URLs, flipping the tag on release day would silently ship breaking changes to all of them. So 2.x stays latest, 4.0 lives under next, and the tags won't flip until early 2027. htmx 2, like htmx 1 and intercooler.js before it, is supported "in perpetuity."
Compare that to the upgrade treadmill where staying one major behind makes you a straggler within months. One independent reviewer put it well: "The release discipline is the story I will remember: a project that shipped a breaking major and then deliberately kept it off the latest tag so nobody gets upgraded by accident." The team frames the whole release as preparing htmx apps to be "100-year web services." Roll your eyes at the century talk, but the mechanics back it up.
htmx 4 vs Alpine.js: complements, not competitors
Now the comparison this release inevitably invites. The two libraries look similar on paper — tiny, MIT-licensed, attribute-driven — but they solve opposite problems:
| Dimension | htmx 4.0 | Alpine.js 3.x |
|---|---|---|
| Model | Server-driven hypermedia: HTML over the wire | Client-side reactivity: "islands" of behavior in markup |
| Source of truth | The server response | Browser-side state (x-data) |
| Best at | Forms, lists, dashboards, CRUD without an SPA | Dropdowns, toggles, modals, local UI state |
| Bundle (2026) | ~16 KB min+gzip, zero deps | ~7.1 KB gzipped |
| GitHub stars | ~48,100 | ~31,600 |
| npm weekly downloads | ~158,000 | ~498,000 |
The community numbers tell a fun story: htmx wins the conversation (roughly 3× the stars, and all the long-form essays), while Alpine wins the install base (roughly 3× the weekly downloads, largely because it ships inside so many server-rendered stacks by default). Runtime profiles differ too: Alpine evaluates reactive expressions on every state change, while htmx mostly triggers fetches and innerHTML swaps — htmx tends to be lighter on CPU once loaded, Alpine wins on low-bandwidth connections since it avoids round trips.
And here's the twist: 4.0 makes the two closer, not further apart. The release ships an official hx-alpine-compat extension that "smooths over compatibility issues between htmx and Alpine.js," the hx-history-cache extension is explicitly designed to integrate with Alpine, and hx-live openly borrows its ideas. Gross is blunt that this is the intended split of labor: "htmx will never support a fully featured scripting mechanism in core, we recommend something like Alpine.js for that." The pattern experienced developers land on: htmx for everything that touches the server, Alpine for purely local UI state — a combined footprint still smaller than a single React component tree.

Verdict: should you migrate?
- New project on a server-rendered stack? Start on 4.0. The defaults are better — explicit inheritance, swappable error responses with
hx-statusrouting, history that can't go stale, streaming for free. Pin the version:npm install htmx.org@nextor the 4.0.0 CDN URL. - Existing 2.x app? There is no clock — htmx 2 is supported indefinitely, in writing. Run
npx [email protected] upgrade-check -- ./your-projectto size the work. If the report is short, an afternoon in a git worktree settles it; if it's long, stay put guilt-free. One practical tip from the migration guide: do thehx-disable→hx-ignorerename first, before thehx-disabled-eltrename. - Using Alpine today? Nothing changes — and
hx-alpine-compatplushx-livemake the pairing more official than ever.
htmx 4.0 is what a mature project's rewrite looks like: enough courage to break the things that deserved breaking, enough discipline to give everyone years to move, and enough humor to skip a version number on a technicality. The 14KB library didn't just update its internals — it re-upped its bet that HTML, sent over the wire by a boring server, is still the most durable UI technology we have.
Sources
- htmx 4.0.0 has been released! — official announcement, August 28, 2026
- The fetch()ening — Carson Gross's essay announcing htmx 4.0 (November 2025)
- What's New in htmx 4 — official migration guide and change catalog
- htmx 4: What's New, What Breaks, Why It's Not latest — Dennis Morello
- HTMX vs Alpine.js for Solo Developers (2026) — SoloDevStack
- HTMX and Alpine.js: How to combine two great, lean front ends — InfoWorld
- HTMX vs Alpine.js: When to Use Which — OpenReplay