Loading theme preference
Ilia Bukin
← Back to blog

htmx 4.0 is here: `fetch()` under the hood, explicit inheritance, and morph swaps in the core

Aug 29, 2026

htmx shipped 4.0.0 today, closing out eight months of work1. The headline story is under the hood: the library finally dropped XMLHttpRequest for the native fetch() API. From a user's point of view, though, that migration was mostly a means to an end. It opened the door to a rewritten extension system, streaming HTML, and a couple of long-requested features that now ship in the core.

Notably, htmx 4 is deliberately not a break-everything rewrite. The team has been upfront that 4.0, from a user's perspective, is almost identical to htmx 2, with three major changes instead of a clean-slate API.1

The three big changes

Attribute inheritance is explicit now. In htmx 2, many attributes inherited down the DOM tree by default. That behavior came from the intercooler.js days and, the maintainers admit, worked out about the same as CSS: powerful but hard to follow. In htmx 4 you have to opt in by adding an :inherited suffix:

<!-- htmx 2: implicit -->
<div hx-confirm="Are you sure?">
  <button hx-delete="/item/1">Delete</button>
</div>

<!-- htmx 4: explicit -->
<div hx-confirm:inherited="Are you sure?">
  <button hx-delete="/item/1">Delete</button>
</div>

The announcement calls this the single biggest upgrade item, and it's the main thing the supplied tooling is built to hunt down. There's also :append, for adding to an inherited value rather than replacing it, and a htmx.config.implicitInheritance switch to restore the old behavior.12

Event names are standardized. The names htmx 2 accumulated organically are cleaned up into a consistent htmx:phase:action[:sub-action] pattern. htmx:beforeRequest becomes htmx:before:request, htmx:afterSwap becomes htmx:after:swap, and so on. Most error events collapse into a single htmx:error, HTTP error responses fire htmx:response:error, and the htmx:xhr:* events are gone entirely since there's no XHR anymore. The htmx:validation:* events are removed in favor of native browser form validation.12

History no longer uses localStorage. Snapshotting pages into localStorage was a long-running source of support headaches: third-party JavaScript could mutate the DOM, and on restore those mutations lingered without the underlying logic. htmx 4 instead re-fetches the page on back navigation and swaps it into <body>, or into the [hx-history-elt] element if one is present. If you want local caching anyway, a hx-history-cache extension restores history from sessionStorage and is designed to integrate with Alpine.js-style scripting.1

Inside the migration to fetch()

The move away from XHR is permanent (the docs note it cannot be reverted), and every request now uses the native fetch() API.2 Beyond the internals, this is what made the burst of new extensions possible, and it changed a few defaults along the way. The ones most likely to bite during an upgrade:

  • Error responses now swap. htmx 2 wouldn't swap 4xx/5xx responses; htmx 4 swaps all responses except 204 and 304. If your server returns HTML with a 422 or 500, it now lands in the target, so design error responses as swap content, or control them with hx-status.
  • hx-delete no longer includes form data, matching hx-get. Add hx-include="closest form" where you relied on it.
  • A default 60-second timeout now applies via htmx.config.defaultTimeout; htmx 2 had none.
  • OOB swap order flipped: the main content swaps first, then out-of-band and <hx-partial> elements in document order.
  • hx-trigger's queue modifier is gone, folded into hx-sync.

New in the core: morph swaps and <hx-partial>

Two features now ship out of the box.

Morphing swaps (hx-swap="innerMorph" and hx-swap="outerMorph") merge new HTML into the existing DOM using the idiomorph algorithm instead of replacing it wholesale, which is generally better at preserving state in complex UIs. Idiomorph is something the team created and almost included in 2.x before holding off.12

<hx-partial> lets one response target multiple elements, and reads more clearly than out-of-band swaps when you want explicit control over both the target and the swap strategy:

<hx-partial hx-target="#messages" hx-swap="beforeend">
  <div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
  <span>5</span>
</hx-partial>

There are also new swap styles (textContent, delete), shorthand aliases (before, after, prepend, append), per-status-code swap control via hx-status with support for exact codes and wildcards, and View Transitions API support, disabled by default.

An extension boom and streaming HTML

Moving to fetch() let the team rethink the extension model, and 4.0 ships a pile of new core extensions, including hx-preload (preload on hover), hx-download (fetch-based file downloads), hx-alpine-compat (running htmx alongside Alpine.js), and hx-history-cache. There's also htmx-2-compat for anyone who'd rather keep the 2.x event names and defaults for now. The SSE and WebSocket extensions were significantly rewritten, and streaming HTML gets three paths: hx-sse over text/event-stream, hx-ws over WebSockets, and hx-multipart over multipart/mixed.12

The team also took its first crack at its own scripting layer. hx-live is a front-end scripting solution inspired by Alpine.js, jQuery, and hyperscript. If you'd rather not think about which extensions to grab, a new htmax.js bundle packages htmx with the most popular ones in a single file.1

The rollout is the interesting part

htmx 4.0 is not being tagged latest on npm, specifically so the team doesn't force-upgrade people relying on non-versioned CDN URLs. The 2.x line stays latest and 4.0 stays next until early 2027, while the website itself now references 4.0. And 2.x will be supported indefinitely, so there's no pressure to rush.1

To ease the move, the team shipped a command-line upgrade checker that scans templates and flags inherited attributes, renamed events, and removed APIs:

$ npx [email protected] upgrade-check -- ./templates

It flags things like an hx-headers that needs an :inherited suffix to keep working as a CSRF header, or the hx-disable to hx-ignore rename. There are also LLM-oriented skill files covering guidance, debugging, extension authoring, and migrating from 2.x, a sign of how much of the ecosystem now depends on coding agents.1

Install is the usual one-liner:

<script src="https://unpkg.com/[email protected]/dist/htmx.min.js"></script>

htmx's pitch has always been a middle path between server-rendered pages and a full JavaScript SPA, and 4.0 reads as consolidation rather than a rewrite: the behavior differences from 2.x are small by design, and where they diverge the choices are explicit and documented. The rollout is in the same spirit. If your site loads htmx from an unversioned CDN URL, nothing moves under you, and 2.x keeps support indefinitely.


  1. htmx 4.0.0 release announcement
  2. What's New in htmx 4