DEV Community

Ashraf
Ashraf

Posted on

Htmx 4.0 Is Here — Everything You Need to Know About the Upgrade (and Why It Matters)

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

The team shipped an upgrade checker to find where you need :inherited:

npx htmx.org@4.0.0 upgrade-check -- ./templates
Enter fullscreen mode Exit fullscreen mode

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 fire htmx: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">
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 of htmx.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-disable now means "disable element during request." What was hx-disable in htmx 2 should be renamed to hx-ignore.
  • Timeout is now configurable: htmx.config.defaultTimeout defaults to 0 (no timeout). Set it per-request with hx-request='{"timeout": 5000}'.
  • Extension loading is explicit: Include extension scripts directly in <script> tags, no attribute needed. You can restrict which extensions load via htmx.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 via htmx.config.defaultSettleDelay.
  • :request context: No more event.detail spelunking — 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>
Enter fullscreen mode Exit fullscreen mode

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)