
OK, I said there would never be a version three of htmx.
But, technically, I never said anything about a version four…
In The Future of htmx I said the following:
We are going to work to ensure that htmx is extremely stable in both API & implementation. This means accepting and documenting the quirks of the current implementation.
Earlier this year, on a whim, I created fixi.js, a hyperminimalist implementation
of the ideas in htmx.  That work gave me a chance to get a lot more familiar  with the fetch() and, especially, the
async infrastructure
available in JavaScript.
In doing that work I began to wonder if that, while the htmx API is (at least reasonably) correct, maybe there was room for a more dramatic change of the implementation that took advantage of these features in order to simplify the library.
Further, changing from ye olde XMLHttpRequest
(a legacy of htmx 1.0 IE support) to fetch() would
be a pretty violent change, guaranteed to break at least some stuff.
So I began thinking: if we are going to consider moving to fetch, then maybe we should also that this update as a chance address at least some of the quirks that htmx has acquired over its lifetime.
So, eventually & reluctantly, I have changed my mind: there will be another major version of htmx.
However, in order to keep my word that there will not be a htmx 3.0, the next release will instead be htmx 4.0.
With htmx 4.0 we are rebuilding the internals of htmx, based on the lessons learned from fixi.js and five+ years of supporting htmx.
There are three major simplifying changes:
The biggest internal change is that fetch() will replace XMLHttpRequest as the core ajax infrastructure.  This
won’t actually have a huge effect on most usages of htmx except that the events model will necessarily change due
to the differences between fetch() and XMLHttpRequest.
I feel that my biggest mistake in htmx 1.0 & 2.0 was making attribute inheritance implicit. I was inspired by CSS in doing this, and the results have been roughly the same as CSS: powerful & maddening.
In htmx 4.0, attribute inheritance will be explicit rather than implicit, via the :inherited modifier:
  <div hx-target:inherited="#output">
    <button hx-post="/up">Like</button>
    <button hx-post="/down">Dislike</button>
  </div>
  <output id="output">Pick a button...</output>
Here the hx-target attribute is explicitly declared as inherited on the enclosing div and, if it wasn’t, the
button elements would not inherit the target from it.
This will be the most significant upgrade change to deal with for most htmx users.
Another constant source of pain for both us and for htmx users is history support. htmx 2.0 stores history in local cache to make navigation faster. Unfortunately, snapshotting the DOM is often brittle because of third-party modifications, hidden state, etc. There is a terrible simplicity to the web 1.0 model of blowing everything away and starting over. There are also security concerns storing history information in session storage.
In htmx 2.0, we often end up recommending that people facing history-related issues simply disable the cache entirely, and that usually fixes the problems.
In htmx 4.0, history support will no longer snapshot the DOM and keep it locally. It will, rather, issue a network request for the restored content. This is the behavior of 2.0 on a history cache-miss, and it works reliably with little effort on behalf of htmx users.
We will offer an extension that enables history caching like in htmx 2.0, but it will be opt-in, rather than the default.
This tremendously simplifies the htmx codebase and should make the out-of-the-box behavior much more plug-and-play.
Most things.
The core functionality of htmx will remain the same, hx-get, hx-post,
hx-target, hx-boost, hx-swap, hx-trigger, etc.
Except for adding an :inherited modifier on a few attributes, many htmx projects will “just work” with htmx 4.
These changes will make the long term maintenance & sustainability of the project much stronger. It will also take pressure off the 2.0 releases, which can now focus on stability rather than contemplating new features.
That said, htmx 2.0 users will face an upgrade project when moving to 4.0 in a way that they did not have to in moving from 1.0 to 2.0.
I am sorry about that, and want to offer three things to address it:
latest and htmx 3.x is nextOf course, it isn’t all bad. Beyond simplifying the implementation of htmx significantly, switching to fetch also gives us the opportunity to add some nice new features to htmx
By switching to fetch(), we can take advantage of its support for
readable streams, which
allow for a stream of content to be swapped into the DOM, rather than a single response.
htmx 1.0 had Server Sent Event support integrated into the library. In htmx 2.0 we pulled this functionality out as an extension. It turns out that SSE is just a specialized version of a streaming response, so in adding streaming support, it’s an almost-free free two-fer to add that back into core as well.
This will make incremental response swapping much cleaner and well-supported in htmx.
Three years ago I had an idea for a DOM morphing algorithm that improved on the initial algorithm pioneered by morphdom.
The idea was to use “id sets” to make smarter decisions regarding which nodes to preserve and which nodes to delete when merging changes into the DOM, and I called this idea “idiomorph”. Idiomorph has gone on to be adopted by many other web project such as Hotwire.
We strongly considered including it in htmx 2.0, but I decided not too because it worked well as an extension and htmx 2.0 had already grown larger than I wanted.
In 4.0, with the complexity savings we achieved by moving to fetch(), we can now comfortably fit a morphInner and
morphOuter swap into core, thanks to the excellent work of Michael West.
htmx has, since very early on, supported a concept of “Out-of-band” swaps: content that is removed from the main HTML response and swapped into the DOM elsewhere. I have always been a bit ambivalent about them, because they move away from Locality of Behavior, but there is no doubt that they are useful and often crucial for achieving certain UI patterns.
Out-of-band swaps started off very simply: if you marked an element as hx-swap-oob='true', htmx would swap the element
as the outer HTML of any existing element already in the DOM with that id.  Easy-peasy.
However, over time, people started asking for different functionality around Out-of-band swaps: prepending, appending, etc. and the feature began acquiring some fairly baroque syntax to handle all these needs.
We have come to the conclusion that the problem is that there are really two use cases, both currently trying to be filled by Out-of-band swaps:
Therefore, we are introducing the notion of <partial>s in htmx 4.0
A partial element is, under the covers, a template element and, thus, can contain any sort of content you like.  It
specifies on itself all the standard htmx options regarding swapping, hx-target and hx-swap in particular, allowing
you full access to all the standard swapping behavior of htmx without using a specialized syntax.  This tremendously
simplifies the mental model for these sorts of needs, and dovetails well with the streaming support we intend to offer.
Out-of-band swaps will be retained in htmx 4.0, but will go back to their initial, simple focus of simply replacing an existing element by id.
htmx 2.0 has have View Transition support since April of 2023. In the interceding two years, support for the feature has grown across browsers (c’mon, safari, you can do it) and we’ve gain experience with the feature.
One thing that has become apparent to us while using them is that, to use them in a stable manner, it is important to establish a queue of transitions, so each can complete before the other begins. If you don’t do this, you can get visually ugly transition cancellations.
So, in htmx 4.0 we have added this queue which will ensure that all view transitions complete smoothly.
CSS transitions will continue to work as before as well, although the swapping model is again made much simpler by the async runtime.
We may enable View Transitions by default, the jury is still out on that.
A wonderful thing about fetch() and the async support in general is that it is much easier to guarantee a stable
order of events.  By linearizing asynchronous code and allowing us to use standard language features like try/catch,
the event model of htmx should be much more predictable and comprehensible.
We are going to adopt a new standard for event naming to make things even clearer:
htmx:<phase>:<system>[:<optional-sub-action>]
So, for example, htmx:before:request will be triggered before a request is made.
Another opportunity we have is to take advantage of the async behavior of fetch() for much better performance in our
preload extension (where we issue a speculative (GET only!) request in anticipation of an actual trigger).  We have
also added an optimistic update extension to the core extensions, again made easy by the new async features.
In general, we have opened up the internals of the htmx request/response/swap cycle much more fully to extension developers,
up to and including allowing them to replace the fetch() implementation used by htmx for a particular request.  There
should not be a need for any hacks to get the behavior you want out of htmx now: the events and the open “context” object
should provide the ability to do almost anything.
hx-on SupportIn htmx 2.0, I somewhat reluctantly added the hx-on attributes to support light
scripting inline on elements.  I added this because HTML does not allow you to listen for arbitrary events via on
attributes: only standard DOM events like onclick can be responded to.
We hemmed and hawed about the syntax and so, unfortunately, there are a few different ways to do it.
In htmx 4.0 we will adopt a single standard for the hx-on attributes: hx-on:<event name>.  Additionally, we are
working to improve the htmx JavaScript API (especially around async operation support) and will make those features
available in hx-on:
<button hx-post="/like"
        hx-on:htmx:after:swap="await timeout('3s'); ctx.newContent[0].remove()">
    Get A Response Then Remove It 3 Seconds Later
</button>
htmx will never support a fully featured scripting mechanism in core, we recommend something like Alpine.js for that, but our hope is that we can provide a relatively minimalist API that allows for easy, light async scripting of the DOM.
I should note that htmx 4.0 will continue to work with eval() disabled, but you will need to forego a few features like
hx-on if you choose to do so.
All in all, our hope is that htmx 4.0 will feel an awful lot like 2.0, but with better features and, we hope, with fewer bugs.
As always, software takes as long as it takes.
However, our current planned timeline is:
htmx@4.0.0-alpha1latest in early-2027ishYou can track our progress (and see quite a bit of dust flying around) in the four branch on
github and at:
Thank you for your patience and pardon our dust!
“Well, when events change, I change my mind. What do you do?” –Paul Samuelson or John Maynard Keynes