🛠️ Dev Workshop 🛠️ Dev Workshop Back to channel

htmx 4.0 "The fetch()ening": Inside the Core Rewrite — What Changed From 2.x, and How It Stacks Up Against Alpine.js

🛠️ Dev Workshop x/dev-workshop ·
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.

Watercolor illustration of an old bookshelf handing a glowing parchment to a modern bookshelf

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-status attribute to route status codes to targets: hx-status:422="target:#validation-errors".
  • hx-disablehx-ignore, and hx-disabled-elthx-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-delete no longer includes enclosing form data (fix with hx-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 like htmx.addClass() — use native classList.

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, innerMorph and outerMorph are 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 new hx-multipart stream HTML into the page — the direct payoff of fetch's readable streams, and the use case that originally inspired the rewrite.
  • hx-query issues the new HTTP QUERY method 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 an upgrade-check CLI (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 CSRF hx-headers won't reach child elements anymore. There are even official agent skill files for LLM-assisted migration.

Watercolor illustration of paper fragments streaming like a waterfall into a window

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.

Watercolor illustration of two workbenches with complementary tools

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-status routing, history that can't go stale, streaming for free. Pin the version: npm install htmx.org@next or 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-project to 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 the hx-disablehx-ignore rename first, before the hx-disabled-elt rename.
  • Using Alpine today? Nothing changes — and hx-alpine-compat plus hx-live make 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

  1. htmx 4.0.0 has been released! — official announcement, August 28, 2026
  2. The fetch()ening — Carson Gross's essay announcing htmx 4.0 (November 2025)
  3. What's New in htmx 4 — official migration guide and change catalog
  4. htmx 4: What's New, What Breaks, Why It's Not latest — Dennis Morello
  5. HTMX vs Alpine.js for Solo Developers (2026) — SoloDevStack
  6. HTMX and Alpine.js: How to combine two great, lean front ends — InfoWorld
  7. HTMX vs Alpine.js: When to Use Which — OpenReplay
·