Arms crossed picture of James Auble
HTMX logo

Why I Wont Be Using HTMX (Yet)

PublishedAug 31st, 2023

HTMX has gotten a lot of coverage lately, no doubt in part due to htmx in 100 seconds released by the popular YouTube channel Fireship. The trend of "HTML over-the-wire" or HATEOAS as a whole has also gained in popularity and has likely fed into the efforts and attention given to projects like HTMX and others.

This isn't at all a hit-piece. I have a huge appreciation for all the creators and contributors who make awesome web development tools like HTMX and others. As with any of my other posts that offer criticism or critique--I write to spur discussion that hopefully improves a software or brings about better understanding of the potential and possibilities of using it.

With that out of the way, I want to share a few reasons why after playing with HTMX for a few rounds I find that it doesn't really help productivity for me.

Swapping Doesn't Have to be Difficult 

One of the key features of HTMX is swapping HTML fragments. Wanting to easily grab some HTML from an endpoint and swap it out in-place is probably the reason that many developers have included HTMX in their projects.

I don't often have a lot of praise for Shopify, but I have to give them credit for tipping me off to the technique of serving up fragment on endpoints and swapping out the HTML to save users the full round-trip page reload.

Despite the scripts not very super elegant or easily readable, I appreciated how with a few lines of modern javascript, we could seemlessly swap out a section with updated variant data on the product page when a user interacts with the variant picker. This is the actual function where I first saw this technique used.

To spare you the pain of understanding the context of this function, I'll provide you with the snippet I created to use the same approach with any endpoint that provides an HTML fragment.

Let's take a look.

async function fetchHTML(endpoint) {
  return await fetch(endpoint)
    .then((response) => response.text())
    .then((responseText) => {
      return new DOMParser().parseFromString(responseText, 'text/html')

I intentionally kept the helper function short and sweet. Ai chatbots can do a much better job of explaining what the function does so click here to view that answer using

Most importantly, the function returns a Promise that resolves with a DOM Document representing the fetched HTML document.

Here's an example from one of my projects of how it can be used to swap out search results:

swap(endpoint) {
    (response) => {
      const source = response.querySelector('#search-results')
      const target = document.querySelector('#search-results')

      target.innerHTML = source.innerHTML

      history.pushState({}, '', endpoint)

        behavior: 'smooth',
        block: 'start'

To summarize, I'm just grabbing the stuff I need out of my document that's chillin' in memory and swapping it out with what exists in my page document. Fearing that my user grabs the URL and shares it expecting to see the same result, I update the URL with a URL that when loaded will display the updated content.

To do this with HTMX, I would need to use hx-swap, hx-select, hx-target and then also something like hx-replace-url to swap out the URL.

The problem I find is that I'm always trying to do one final effect that I have to end up using a hook and callback for that makes me have to write plain javascript anyway.

With my fetchHTML() helper function, the possibilities are always endless as it's just modern Vanilla Javascript and it's all in one place, versus writing a bunch of Javascript as a directive expression or splitting things up into markup and scripts.

I Already Use AlpineJS 

This is obviously no knock on HTMX. It's just the case that I already use a micro-framework or whatever you wanna call it to achieve two-way binding and other things. Adding additional overhead (yes 14k is negligible but still) and directives to achieve what I can already do with my snippet + AlpineJS + Vanilla JS just seems like a hard sell.

I have all my event listeners already with AlpineJS, so I can do something like create an AlpineJS component and plop my swap function in it and then call it based on some event like:

<button @click="swap(endpoint)">Swap Me</button>

IMHO, that's a little prettier than

<button hx-get="/fragment-endpoint" hx-swap="innerHTML" hx-select=".search-results" hx-target=".search-results" hx-replace-url ....>Swap Me</button>

HTMX Did Not Play Nice With My Devserver 

I know.... "sounds like a personal problem" right? Still, I found between CORS errors and issues with URL rewrites (or the lack thereof) I seemed to be just running into annoyances where I wouldn't have expected from a tool that just seemed unintrusive and was supposed to make my life easier.


Too many tools and options as a web developer is usually a good problem to have. I actually ended up digging through the HTMX source code and used some of the techniques to improve a more powerful version of my helper swap function. Achieving functionality right in the markup is a popular technique for good reason and although I won't be using HTMX for the time being, I'll definitely be following it to see what happens and how devs are benefiting from it.

Please share your experiences with HTMX in the comments and thanks for reading!

Code on web assassins!

Scrolldown Icon