News

htmx 4.0.0 Ships: fetch() Under the Hood, Explicit Attribute Inheritance, and Morphing Swaps

htmx 4.0.0 is out after 8 months of work. The big internal shift is from XMLHttpRequest to fetch(), attribute inheritance is now opt-in, events are standardized, and history no longer relies on localStorage. Morphing swaps and the new <hx-partial> tag are the headline features.

August 28, 2026· 3 min read· Source: htmx
htmx 4.0.0 Ships: fetch() Under the Hood, Explicit Attribute Inheritance, and Morphing Swaps

htmx 4.0.0 has landed after eight months of work, and the headline is that the library's internals have moved from XMLHttpRequest to fetch(). That shift, which started as an experiment while building fixi and was pushed along by Christian's ideas on streaming HTML, opened the door for a reworked extension system and a handful of new capabilities.

From a user's perspective, htmx 4 is deliberately close to htmx 2. The team says behavioral differences are small, and where they diverge, the choices are explicit. The three major changes are: attribute inheritance is now opt-in rather than implicit, event names have been standardized, and history support no longer uses localStorage by default.

Attribute inheritance: now explicit

In htmx 2, attributes like hx-confirm on a parent element were inherited by children, a behavior inherited from intercooler.js and inspired by CSS. As with CSS, that was powerful but often confusing. In htmx 4, you must mark an attribute as inherited by appending :inherited:

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

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

This is the biggest upgrade burden, so the team ships a command-line tool to flag places that need the :inherited marker. Attributes like hx-disinherit are no longer needed and should be removed.

Events: standardized naming

htmx 2's events grew organically and were hard to track. In htmx 4, all events follow the pattern htmx:phase:action[:sub-action]. For example, htmx:beforeRequest becomes htmx:before:request, and htmx:afterSwap becomes htmx:after:swap. Most error events collapse into htmx:error, with HTTP errors firing htmx:response:error. The htmx:xhr:* events are gone, since htmx now uses fetch(), and htmx:validation:* events are dropped in favor of native browser form validation.

History: no more localStorage

htmx 2 cached page snapshots in localStorage, which caused headaches when third-party JavaScript mutated the DOM and those mutations persisted after restore without the underlying logic. htmx 4 re-fetches the page on back navigation and swaps it into the body (or the [hx-history-elt] element). If you want local caching, there's a new hx-history-cache extension that uses sessionStorage and plays nice with Alpine.js.

New features: morphing and hx-partial

Two big features stand out. First, morphing swaps are now built in, using an improved version of the idiomorph algorithm. Second, the new <hx-partial> tag lets you target multiple parts of the page in a single response, going beyond the single-element replacement of out-of-band swaps:

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

Extensions and the hx-live experiment

The move to fetch() re-energized the extension ecosystem. New extensions include hx-preload, hx-download, hx-alpine-compat, and hx-history-cache. Streaming extensions for SSE, WebSockets, and multipart are updated. The team also introduces hx-live, a small scripting solution inspired by Alpine.js and jQuery, which they describe as DOM-based, HATEOAS-friendly reactivity. A new htmax.js bundle packages htmx with the most popular extensions in one file.

Notably, htmx 4 is not tagged latest on npm. The 2.x line stays latest to avoid breaking users on non-versioned CDN URLs, with 4.0 as next until early 2027. The website, however, will reference 4.0.

In htmx 4 attributes are not inherited unless you explicitly say so by adding an :inherited after the attribute name.
Manul X Editorial
htmx 2 vs htmx 4: Key Differences
At a glance
Aspecthtmx 2htmx 4
HTTP layerXMLHttpRequestfetch()
Attribute inheritanceImplicitExplicit via :inherited
Event naminghtmx:beforeRequest, etc.htmx:before:request, etc.
History cachelocalStorageRe-fetch on back; optional sessionStorage extension
Morphing swapsNot built-inBuilt-in (idiomorph)
Multi-target updatesOut-of-band swaps<hx-partial> tag