Hotwire vs htmx - comparison

Dear community,
how would you compare Hotwire to htmx?

What are some of the key differences? What does currently one project do better over the other? What was your personal experience choosing one over the other?

I am considering using this kind of approach to web development without prior experience with neither and since Hotwire made it to Reddit recently I bet I am not the first to think about this that’s why I wanted to raise this question.

No pun intended,
thanks!

2 Likes

Broadly speaking, I think Hotwire and htmx have similar goals. I haven’t personally used htmx, but I can offer my own biased (:angel:) first impression based on the web site, which opens by asking:

  • Why should only <a> and <form> be able to make HTTP requests?
  • Why should only click & submit events trigger them?

The Hotwire philosophy is progressive enhancement, which I might characterize this way, in response:

If the web offers us guidance, take it. The web gives us <a> to change pages and <form> to change server-side state, so that’s what we’ll use in our app. That way we have fewer decisions to make and we’re responsible for less code.

If we want to do something that can’t be done with HTML and CSS alone, we’ll still try to do as much as we can with HTML and CSS, and use a layer of JavaScript on top for the rest.

Hotwire is a conceptual umbrella that unifies Turbo (formerly Turbolinks, dating back to 2012) and Stimulus (from 2018) with a set of conventions which can be used with any server-side framework.

When paired with a REST back-end like Rails that renders and serves HTML, there’s a clear path for your Hotwire app to cover as much of the progressive enhancement spectrum as you want, without having to maintain multiple codebases.

Your Hotwire app can run on a browser with limited or no JavaScript, on smartphone and desktop browsers of all shapes and sizes and versions, and on native apps for Android and iOS with Turbo Native and Strada (coming soon).

htmx looks like an interesting domain-specific language on top of HTML, but it doesn’t subscribe to the progressive enhancement philosophy, as far as I can tell. There’s no real story for native apps like Hotwire has, and I don’t see how an htmx app could ever work with JavaScript disabled.

Those things may or may not be important to you. We didn’t explicitly build HEY to work with JavaScript disabled, for example—we kind of just get it for free by adopting the Hotwire conventions. But the native apps which sit at the other end of the spectrum are critical to our business, and those are built on the same conventions: links and forms and HTML with the last bit of native fidelity sprinkled on top as progressive enhancement.

9 Likes

I think that’s a reasonable take on htmx v. hotwire. htmx doesn’t focus nearly as much on progressive enhancement, although there is a mechanism for doing so (hx-boost).

htmx is attempting to push HTML to its logical conclusion as a hypertext and, hence, is lower level than what hotwire looks like at first glance. (I’m looking at it as an ousider, so take all this with a grain of salt.) With htmx, the idea is to surface as much as possible in straight HTML and generalize the concept of a hypertext: event triggers, requests, targets for replacement, etc.

I would expect hotwire to work more smoothly out of the box, and kick out to Stimulus when necessary, whereas htmx would likely be more explicit work, but can probably go farther before it is necessary to resort to javascript.

Both reasonable approaches. Regardless of what direction someone decides to go, I’m glad to see HTML-oriented libraries making a resurgence.

9 Likes

I’ll respond as someone who is very familiar with both htmx and Intercooler (the predecessor of htmx).

I would say htmx happens to very nicely support progressive enhancement, if that’s your goal. Take this description of the hx:boost attribute for example:

The hx-boost attribute allows you to “boost” normal anchors and form tags to use AJAX instead. This has the nice fallback that, if the user does not have javascript enabled, the site will continue to work.

But what really sets htmx apart imho, is that you get to update parts of the page dynamically, by just adding some html attributes! I.e. no JavaScript required.

It doesn’t end there of course, it makes implementing many web app staples like lazy loading, type-ahead search, dependent selects, click-to-edit, etc. absolutely trivial to implement (here’s the list of examples: </> htmx ~ Examples).

Then there’s event handling, animation transitions, websockets, sse … the list goes on … ALL through specifying some attributes on bog standard html elements!

The clue is in the name really: htmx… as in extending html. I like to think of it as extending the html spec to bring you everything we’ve come to expect from modern web app development, purely through html, css and your favorite back-end framework.

My personal intercooler/htmx journey began by wanting to “ajaxify” a rather large, traditional MVC, full-page-refresh app, without doing a complete rewrite (it’s a Playframework app, so essentially the Java-based equivalent of Rails).

It was trivially easy to start “sprinkling in” aka progressively enhancing the app, with no backend changes at all, just by adding a few attributes on the existing html elements.

Not long after that, we had transformed the entire app into what feels like any other SPA (not a single full page refresh in sight), complete with history (back-button) support, while it still being a bog standard MVC app, with server side templates being rendered to the frontend. This, as they say, was a MASSIVE win :grin:

Bottom line: if you believe that simplicity beats the “sexy" complexity introduced by heavy JS frameworks like React, Vue, Angular, etc; if you’re a single developer/small team wanting to productively churn out simple, easy to understand code that doesn’t require a bunch of extra build steps; if you buy into philosophies like the idea behind Tailwind CSS, Alpine.js and yes some parts of Hotwire (especially the “html over the wire” part), then I suggest you at least try out htmx before you make a decision.

I’m willing to bet you won’t regret it :slight_smile:

P.S. I Just saw that the guy who made both intercooler and htmx responded while I was writing this, by pretty much saying what I was trying to say, just much more succinctly :joy:

5 Likes

I am in a position, to comment on the same as I have tried using both of them. Here are the pros and cons for both of them:

1. Hotwire:

  1. Very solid transition and cache management. Does things as promised AND as per expectation without gotchas.
  2. Built-In loader component is FAB, it is very important as it helps the user understand the state of the application without dev having to do anything about it.
    Cons:
  • Does not expose any JS API, for use cases and for triggering interactions on events that are <a> click or form submit you have to use JS. Recommended lib being a stimulus. That may not work out in a most elegant way.
  • Bigger size
  • Lack of documentation, only guides are present. If you want to do something more complex not put in text, you have to dig into code to understand the working

2. HTMX

  1. HTMX and hypersript is in the direction towards script in HTML almost.
  2. Provides elegant functions for AJAX calls etc.
  3. Animations are well documented with relevant examples and use cases
  4. Smaller size and simpler implementations
  5. Plugin architecture
    Cons:
  • No caching provided by default.
  • Had a lot of trouble, making it work PROPERLY with web components and in the end did not work. (I faced a lot of parsing issues, your mileage may vary)
  • Uses XMLHTTP request to query data (may not be a con)
  • Did not get a “production-ready” vibe

In the end, I am using Turbo and stimulus. I was desperately in need of JS API to allow me to initiate replacing custom HTML.
So I changed the global fetch function. So to replace some HTML:
I call a helper function which basically does is create a form element using HTML, assigns action as the dummy HTTP endpoint created in my overloaded fetch function.
In that form I create an input field with the value that I want as the response, then I click the button in the form.
The “fetch” API call done, goes through a custom fetch function, and emulates a network call.

2 Likes