Htmx 4.0 Is Here — Everything You Need to Know About the Upgrade (and Why It Matters)
Htmx 4.0 dropped today after 8 months of development, and it's a bigger deal than the semver bump suggests. The internals were rewritten from XMLHttpRequest to fetch(), attribute inheritance is now explicit, morphing swaps are built-in, and there's a new <hx-partial> tag that changes how you think about server-rendered responses. Here's what changed, why, and how to migrate.
The Three Breaking Changes in Htmx 4.0 vs Htmx 2
Htmx 4 keeps the same developer experience on the surface — if you're writing new code with the new defaults, you'll barely notice the difference. But if you're migrating an existing htmx 2 app, there are three breaking changes you need to know about.
1. Attribute Inheritance Is Now Explicit
This is the biggest upgrade burden. In htmx 2, attributes like hx-confirm, hx-target, and hx-headers were silently inherited by children. That was convenient until it wasn't — debugging why a deeply nested button inherited a hx-target from three levels up was a common source of frustration.
In htmx 4, inheritance is opt-in. You add :inherited after the attribute name:
<!-- 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>
You can also append to inherited values instead of replacing them:
<div hx-target:inherited=".container">
<button hx-target:inherited:append=".extra">Click</button>
</div>
The team shipped an upgrade checker to find where you need :inherited:
npx htmx.org@4.0.0 upgrade-check -- ./templates
It scans your templates and reports exactly which attributes need the new suffix. Old attributes like hx-disinherit are no longer needed and should be removed.
Pro tip: If you really want the old implicit-inheritance behavior back (you probably don't, but it's there), set htmx.config.implicitInheritance = true.
2. Event Names Are Standardized
Htmx events in htmx 2 grew organically and were hard to reason about. In htmx 4, all events follow htmx:phase:action[:sub-action]:
| htmx 2 | 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 |
Additional changes:
- Most error events collapse into
htmx:error. HTTP errors firehtmx:response:error. -
htmx:xhr:*events are gone — htmx 4 uses fetch(), not XHR. -
htmx:validation:*events are removed in favor of native browser form validation.
The upgrade checker also flags old event names in hx-on attributes and JavaScript.
3. History No Longer Uses localStorage
Htmx 2 cached page snapshots in localStorage for back-button history. The problem: if a third-party script mutated the DOM after the snapshot was taken, restoring the page would show stale state without the JavaScript logic that created it.
Htmx 4 re-fetches the page on back navigation and swaps it into <body> (or your [hx-history-elt] element). This means third-party scripts "just work" — and with good HTTP caching, it's fast.
If you need local caching (e.g., for Alpine.js integration), there's an hx-history-cache extension that stores history in sessionStorage instead.
Two Big New Features in Htmx 4.0
Morph Swaps (Idiomorph Built-In)
DOM morphing is now a first-class swap strategy. The idiomorph algorithm (created by the htmx author) walks the old DOM and edits it into the shape of the new content, keeping unchanged nodes intact. That preserves focus, text selection, scroll position, video playback, and any state a script or web component placed on the element.
<div hx-get="/updates" hx-swap="morph">
You get two strategies:
-
morph— morphs the target element and its children -
morph:inner— morphs only the children, leaving the target itself untouched
Mark stable elements with id attributes — idiomorph uses them as anchors. You can also freeze elements with hx-morph-freeze or skip children with hx-morph-skip.
For the inner morph specifically, there's also hx-preserve (which was available via extension in htmx 2).
The <hx-partial> Tag
This is the most interesting addition. <hx-partial> is similar to out-of-band swaps but cleaner when you need to update multiple targets from a single response:
<hx-partial hx-target="#messages" hx-swap="beforeend">
<div>New message</div>
</hx-partial>
<hx-partial hx-target="#count">
<span>5</span>
</hx-partial>
Each <hx-partial> declares its own target and swap strategy right in the response HTML. This makes multi-target updates explicit and composable — a significant improvement over the old hx-swap-oob attribute approach where ordering mattered.
One gotcha: In htmx 4, main content swaps first, then OOB/partial elements swap after (in document order). In htmx 2, all swaps happened simultaneously. If your app relies on the old ordering, restructure so each swap is independent.
Internal Rewrite: fetch() Replaces XMLHttpRequest
This is invisible to most users but matters for the library's future. Htmx had been using XMLHttpRequest since its inception for backwards compatibility. Moving to fetch() required a major internal refactoring, but it unblocks:
-
Streaming HTML via
text/event-stream(hx-sse), WebSockets (hx-ws), and multipart/mixed (hx-multipart) - Native file downloads via the hx-download extension
-
Cleaner extension APIs — extensions are now registered with
htmx.registerExtension()instead ofhtmx.defineExtension()
The switch also means hx-delete no longer includes enclosing form data (which makes it consistent with the HTTP spec), and hx-trigger queue modifiers (queue:all, etc.) are removed in favor of hx-sync.
What Else Changed
There are a dozen smaller changes worth knowing:
-
HTMX 2 → 4 rename:
hx-disablenow means "disable element during request." What washx-disablein htmx 2 should be renamed tohx-ignore. -
Timeout is now configurable:
htmx.config.defaultTimeoutdefaults to 0 (no timeout). Set it per-request withhx-request='{"timeout": 5000}'. -
Extension loading is explicit: Include extension scripts directly in
<script>tags, no attribute needed. You can restrict which extensions load viahtmx.config.extensions. -
HTTP error handling: All HTTP responses are now swapped. If your server returns 4xx/5xx with HTML, that HTML gets swapped into the target. Use
hx-swap="[4xx, 5xx]: none"to suppress, or configure per-code behavior viahtmx.config.defaultSettleDelay. -
:requestcontext: No moreevent.detailspelunking — request parameters are available directly on the event object. - Constructable Stylesheets: Indicator CSS now uses Constructable Stylesheets, so no CSP nonce is needed for the indicator styles.
Full migration table: What's New in htmx 4.
The New Extensions Ecosystem
The fetch() rewrite sparked a wave of new extensions:
| Extension | What it does |
|---|---|
hx-preload |
Preload content on mouseover |
hx-download |
Native, fetch-based file downloads |
hx-alpine-compat |
Smooths compatibility with Alpine.js |
hx-history-cache |
Caches history in sessionStorage for Alpine.js |
hx-sse |
Streams HTML over text/event-stream
|
hx-ws |
WebSocket streaming |
hx-multipart |
Multipart/mixed streaming |
There's also hx-live, a new small front-end scripting solution that integrates tightly with htmx. It's inspired by Alpine.js, jQuery, and hyperscript, and supports DOM-based, HATEOAS-friendly reactivity.
If you don't want to think about which extensions to pick, the htmax.js bundle packages htmx with the most popular ones in a single file.
Should You Upgrade?
New projects: Start with htmx 4. The new defaults (explicit inheritance, fetch-based, morph swaps) are better and the ecosystem will converge here.
Existing htmx 2 projects: Run the upgrade checker first to scope the work. The changes are mechanical — mostly adding :inherited suffixes and renaming events. The team says htmx 2 will be supported indefinitely, so there's no rush. Htmx 4 is currently marked as next on npm (not latest) until early 2027.
If you use extensions heavily: Check whether your extensions have been updated for htmx 4. The extension API changed (registration method, event hooks), and many community extensions may need updates.
Installing
<script src="https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js"></script>
Or via npm: npm install htmx.org@4.0.0.
Bottom Line
Htmx 4.0 is a carefully engineered upgrade. The breaking changes are well-motivated and come with tooling to automate the migration. The new features — morph swaps, <hx-partial>, streaming extensions, hx-live — address real friction points in building hypermedia-driven apps. If you're building with htmx, this release solidifies the library's position as the serious alternative to the SPA monoculture.
The full release announcement is at four.htmx.org, and the complete migration guide is at the What's New page.
Sources: htmx 4.0.0 release announcement, What's New in htmx 4, Morphing Swaps Guide, HN discussion (540 pts, 128 comments)
Top comments (0)