It's an amusing solution, but if this ends up being anything like the missing PHP 6, it's also going to cause confusion for users. It might've been better to just mea culpa and release 3.0 anyway. I can't imagine anybody would really hold it against the author.
What's the confusion around PHP 6? - There would have been a lot more confusion due to articles, talk slides, even printed books, .. talking about PHP 6's Unicode support would have been truly confusing.
There are plenty of people with high INT and low WIS. As the old joke goes, intelligence is knowing that a tomato is a fruit. Wisdom is knowing not to put tomatoes in a fruit salad.
From what I know of Carson from his writing and presentations, he probably worded it that way on purpose knowing he'd eventually do a new version, and he didn't want to miss an opportunity to troll everyone a bit.
> htmx 2.0 (like htmx 1.0 & intercooler.js 1.0) will be supported in perpetuity, so there is absolutely no pressure to upgrade your application: if htmx 2.0 is satisfying your hypermedia needs, you can stick with it
This is commendable, specially during times when libraries and programs aren't afraid of breaking changes and API churn.
I think the ideas behind HTMX are cool, and Carson’s advocacy around Hypermedia is great.
I came from SPA-land and was tired of the fundamental architectural issue of having to keep the front-end and back-end state in sync.
I’ve compared Datastar and HTMX and decided on Datastar. There’s overlap between the two libraries in that they both support the request/response model, but with Datastar my learning investment takes me further and opens up new possibilities.
In one project I was able to remove a bunch of polling code and simply push a signal from the server to the browser when an external event occurred. The reduction in complexity was crazy.
On an internal tool I noticed I didn’t need Alpine.js anymore, and while anytime I can remove a dependency is a good time, the conceptual simplicity is what really makes me happy.
Now I’m doing a small app where I decided to make a streaming connection between browser and server and simply regenerate and send the entire view over that connection anytime something changes. Intuitively this felt wasteful but it turned out that with compression this works beautifully. There’s just less code in my app, and the code that’s there is less “fiddly” not having to deal with partial updates of the page.
If you’re coming from the world of SPAs, definitely check out both.
One of the things I had to wrap my head around was the whole async server style of programming. I learned that doing a sleep on the server consumes essentially zero resources. The thread goes to sleep and wakes up when the timer expires. In the meantime the server can handle other requests unhindered. You can have hundreds or thousands of these threads at the same time - serving requests, waiting for IO, etc.
It’s not like synchronous servers e.g. Flask with Gunicorn (in the Python world) where you have a few pre-forked server processes. In that case it would be a waste to prevent one of those processes from serving other requests while sleeping, I agree.
Ultimately it’s cool though to be able to control the client from the backend asynchronously, updating signals (think of them as a kind of special front-end variable with superpowers) when something on the backend happens, or updating the client’s DOM, or running scripts.
Sleeping on the server still consumes resources like memory, open sockets, etc, which may in turn cause some unnecessary CPU usage because of e.g. GC costs, scheduling costs, etc.
To a point, but sleeping before redirecting on the server is insanity. It's pointlessly brittle when the client is perfectly capable of managing its own timer.
This approach completely breaks sensible reasoning about what clients are responsible for. What's next, sending every keystroke to the server so it can tell the client to update a text input?!?
It still takes resources to keep SSEs open, load balance, reopen lost connections, etc. But worst of all is shifting fundamental logic back to the server for literally no benefit.
It's a toy example, it's suppose to show that you can be doing N things and then redirect. If you have a normal redirect immediately, just do a 307 or whatever.
> 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.
So confusing. I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
UPDATE: or "inherit", sounds like a command, little less confusing.
I was going to make the same comment. "inherited" is the wrong word. I like "inherit" too. And it's a bit shorter which is always nice. I guess "bequeath" might work also?
> I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
That is one interpretation, but not the one I had reading it. I read it as "This target will be inherited by its children," rather than "This target is inherited from its parents," which, while a grammatical possibility, doesn't really make sense because you are specifying it right there with the equal sign.
Both "inherit" and "inheritable" have the same possible double readings. Something like "pass-to-children" or "pass-down" would remove the possible ambiguity, but I'm not sure like either better than "inherited".
I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
This feels like a repeat of the Python 3.0 strategy, though obviously at a much smaller scale. Some stuff is of course hard to roll out but to me it feels "obvious" that having a 4.0 with the inheritence change (or even better, a 2.1 with a toggle on that change!), then a 5.0 with some other changes, then a 6.0 with other changes... all feels way easier to manage than a 4.0 with all the changes at once.
We have version pinning! People who want 2.0 can have 2.0 "forever", so version numbers that go up very high are not actually a problem. Many releases can of course be a bit of a frustration from a release maker's perspective, but given that htmx is the way it is (ain't even getting type checking helping you out on any of this like you would with React!), having the gradual path seems way better.
"I think I've handled the 10 changes in between 2.0 and 4.0... but forgot the 11th change" is a constant annoyance with these huge major version bumps.
I will once again point to the Django strategy of having breaking changes be rolled out over several releases, including in between releases where "both" models exist. It is a very nice way to do release management when rolling things out, gives good checkpoints for pushing things out, and overall means users are seeing less bugs. Going from `XMLHttpRequest` to `fetch` really might not be a feasable thing to toggle, but a lot of the other stuff in that list feels like it.
>I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
Yeah this is a huge fallacy. If you do a breaking change, you want to do as few breaking changes at once as possible, with as little impact as possible and in the best case scenario you're in contact with every single person that is affected by the breaking change, so that it can be handled without anyone noticing anything other than having to bump a library version, which they were doing anyway since they're running a new Python version in the first place.
The dumbest change in python 3 was removing the print statement [0]. Maximum breakage, minimum gain. They could have introduced the new function as println() and deprecated the print statement in Python 3.
> 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.
Based on this section, it will be interesting to see how this evolves. I've used HTMX a bunch but after stumbling on Datastar I've come to prefer it. Partially because I don't need something like alpine.js to get some frontend goodies but also because I've come to appreciate the power of SSE streaming morphable targets to the browser
Great to see this evolution! Realtime ssr hypermedia is definitely the future.
But, my thoughts immediately go to Datastar, which has Fetch, SSE, declarative signals and js expressions, dom morphing, and much more - in a tiny package. I find it to have a more flexible, expressive and standards-compliant API as well. And it'll soon have a simple reactive web components and css framework as well.
At this point, why use HTMX when it really seems like (a heavier) Datastar-lite?
Because Datastar Pro isn't FOSS, and speaking from a purely probabilistic and historical standpoint, your odds of getting rugpulled (in some form or another) might as well be 100%.
I can appreciate why people would have this perspective - I have been rugpulled a few times recently by thoroughly unscrupulous companies (Augment Code are trash humans).
But Datastar is different. The project is literally owned by a 501c3 non-profit. The devs have dayjobs and donate their spare time to this. Funds are for going to conferences or hosting their own
And 99% of the features/value that I mentioned is MIT licensed, and the "rugpulled" code is still available to easily port via the plugin API.
Ah yes, one of the world's most valuable companies is totally a solid parallel for a few guys part-time making a simple, niche js framework whose pro license they discourage people from purchasing... Totally.
As much as I like Delaney, the discord server and Datastar I find this question baffling.
If the Postgres team released PGPro, swore it just contained anti patterns and you can just write the code yourself if you needed that feature, you’d roll your eyes, no?
It’s about the fact they went there, not about the intentions.
> why use HTMX when it really seems like (a heavier) Datastar-lite?
The reason to use htmx is that it has a simpler interface optimized for the majority use-case.
With htmx, you are largely tied to a request/reply paradigm. Something happens that triggers a request (e.g. user clicks a button, or some element scrolls into view), htmx sends the request, and then it processes the response. The htmx interface (`hx-get`, hx-trigger`) is optimized to make this paradigm extremely simple and concise to specify.
Datastar's focus (last I checked) is on decoupling these two things. Events may stream to the client at any time, regardless of whether or not they were triggered by a specific action on the client, and they get processed by Datastar and have some effect on the page. htmx has affordances for listening to events (SEE extension, new fetch support) and for placing items arbitrarily on the page (out-of-band swaps) but if your use-case is a video game or a dashboard or something else where the updates are frequently uncorrelated with user actions, Datastar makes a lot of sense. It's a bit like driving a manual transmission.
Delaney is fond of saying that there's no need for htmx when Datastar can technically do everything htmx can [0]. But I think this misses the point of what makes htmx so popular: most people's applications do fit within a largely request/reply paradigm, and using a library that assumes this paradigm is both simpler to implement and simpler to debug. As an htmx maintainer, I often encourage people to even use htmx less than they want to, because the request/reply paradigm is very powerful and the more you can adhere to browser's understanding of it, the more durable and maintainable your website will be [1].
Moreover, if htmx's real value is ajax request/response, then why are you introducing SSE as a first-class citizen now?
2. Datastar has data-on, and various other attributes, that allow for triggering far more actions than just backend requests, from far more (any) events. I'm glad to see that htmx is now following suit with hx-on, even if it is apparently limited in capabilities.
3. Datastar can do OOB-swaps just fine - that's literally the core functionality, via (their own, faster) idiomorph.
4. Its a misnomer that Datastar is for video games etc - again, as described above, it can do all of the simple things that that HTMX can do, and more. And, again, why is HTMX introducing SSE if its so apparently unnecessary and unwieldy?
5. What makes htmx popular is that it was the first library to make declarative fragment swapping easy. And Carson is just a god-tier marketer. Its nice to see that he's now realized that Delaney was on to something when he wanted to introduce all of these v4 features to HTMX 3 years ago, but was (fortunately for us happy users) forced to go make Datastar instead.
6. We havent even talked about one of the key features - declarative signals. Signals are justifiably taking over all of the JS frameworks and there's even an active proposal to make them part of the web platform. D* makes them simpler to use than any of them, and in a tiny package.
I, Delaney, and all other D* users are grateful for HTMX opening this door. But I reiterate my original question - now that HTMX is becoming Datastar-lite, why not just use Datastar given that the powerful extras don't add any complexity and comes in a smaller package?
And here's the datastar one, edited for parity: [1]
<button data-on:click="@get('/contact/1/edit')">
The htmx one is simpler. There's fewer mini-languages to learn and the API makes more assumptions about what you want. As you noted, Datastar has more generalized mechanisms that are certainly less clunky than htmx's if you lean heavily into more signals- or event-driven behavior, but for (what I believe to be) the majority use-case of a CRUD website, htmx's simpler interface is easier to implement and debug.(For example: you will see the response associated with the request in the browser network tab; I'm not sure if Datastar has a non-SSE mode to support that but it wouldn't be true for SSE.) To each their own.
As for "well then why implement X, Y, or Z," as the OP notes, refactoring to use fetch() means you get them largely for free, without compromising the nice interface. So why not?
That tiny difference is hardly a reason not to use datastar, especially when it brings SO MUCH more useful stuff - all in a smaller package.
Moreover, the fact that Datastar is more generalized is actually better - HTMX has vastly more (non-standards-compliant) attributes that you need to learn.
> I'm not sure if Datastar has a non-SSE mode to support that but it wouldn't be true for SSE.) To each their own.
My first point was literally saying that it has non-SSE and linked to the docs. You're not even trying to be objective here...
> So why not?
Yes, I have no problem with these things being implemented in v4. In fact, celebrated it in my original post. I brought it all up because you were describing that all as needless complexity in Datastar, but now you're implementing it.
Also, most of Datastar can be trivially disabled/unbundled because its nearly all plugins. That is largely not the case for HTMX.
Thus far, you've simply strongly confirmed my initial hunch that HTMX v4 is unnecessary compared to Datastar.
Which is why HTMX is having to bolt on more gubbins. Because, although it's less characters to type its fundamentally complected and therefore less composable.
I'm sure you've already warched it but if you haven't I'd recommend Rich Hickey's talk Simple made Easy.
> Why bother with v4 at all? If it dilutes that simpler interface?
v4 makes almost no changes to the interface, other than to flip inheritance to be off by default.
> I think that even with req/resp morph leads to a simpler majority use case and that's what Turbo and Datastar have both shown. No?
Although you can use the idiomorph extension for htmx, I personally don't think idiomorph is simpler, because there's an algorithm choosing what parts of the page get replaced based on the server response; I prefer to specify exactly what parts of the page get replaced in much simpler terms, a CSS selector, with `hx-target`.
Per [1] above, my style is minimize partial page responses wherever possible, so the ones that I do have are bespoke and replace a specific thing.
Yes! I expect that I will mostly be sticking to `hx-target` though, for the reasons stated above.
My interest in htmx is more on the coarse-grained aspects of its interface, not the finer ones, which is a consistent theme in my writings about it [0].
hey Alex, I hope you are well. Datastar has had direct support for req/rep of HTML, JS, JSON while still morphing for a quite a while. They allow you to go as coarse as you want. Give the size and ability to choose what plugins you actually need seems like Datastar is more in line with your wants at this point. Strange times.
One important difference that I found is that HTMX (and Alpine AJAX) can easily push a URL into the browser's location history. I've used this feature often with my Django projects--essentially storing the state in the URL which is great for sharing URLs or bookmarking them. As far as I'm aware, Datastar have locked this feature behind the "Pro" paywall.
Fair. Though, D*'s authors are pretty adamant that this is an anti-pattern - that's why they put it behind the paywall.
Moreover, the FOSS code still exists and would take 2 minutes to update to the current plugin API (I have Datastar pro and the code is almost exactly the same)
> D*'s authors are pretty adamant that this is an anti-pattern
The Datastar authors are wrong about this. History push is a very important part of the hypermedia-driven application approach. Because URLs are super important. And we want to make sure that the correct URL is shown for the currently-loaded view, and that the view is reproducible given the URL (as much as possible) so that bookmarking and copy-pasting to send URLs just works as expected.
Thanks very much for a thoughtful reply and link to your article. I look forward to reading it.
As it turns out, I shared that very same article in the Datastar discord the other day! Here's some other good ones that I found while digging into the topic, for anyone who cares.
I strongly agree that good urls are very important. But I don't see how D* prevents correct urls/history at all... You can click anchor links just fine for pages that are genuinely separate pages. If its just a sub page, filter etc, then i think in many cases it should only swap into the dom without updating the history.
Moreover, am I wrong to think that if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark? That wouldn't happen with non-boosted anchor links.
Anyway, I'm not the best person to take up this argument. If you are interested at all in some respectful debate on the topic, it would be great if you came by the datastar discord where there's definitely people who would be better able to engage with it. I'd be eager to observe from the sidelines
"Moreover, am I wrong to think that if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark? That wouldn't happen with non-boosted anchor links."
It's a common pattern with Django and template partials that you check if the request is an AJAX request, in which case you just load a partial template to swap into the existing DOM. Or if it's not an AJAX request, your server-side logic loads the full template.
A simple example would be a to-do list at http://example.com/todo/. Clicking on a task item would swap it into the DOM without a full page load, and then you'd update the URL and browser history to http://example.com/todo/my-task/. Then if you open that URL in a new session, your server side logic would render your page with the "my-task" already selected.
> If its just a sub page, filter etc, then i think in many cases it should only swap into the dom without updating the history.
It depends. If it's a 'resource' (in the REST sense) then it should actually push the URL of the resource into history, because the URL should correspond to the currently viewed resource. This is exactly what I was talking about earlier, it's super important as a basic hypermedia principle.
> if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark
See amanzi's explanation or my blog post where I explain the same thing. With htmx we can easily check for the presence of a request header in the backend and serve the appropriate version of the resource: either a partial (fragment) rendering or a full page that contains the resource. I highly recommend reading my blog post: it's not a huge commitment and it will clarify these issues for you.
* Datastar was re-written from the ground up, numerous times.
* They didn't want to update and maintain the plugins that they viewed as unnecessary/anti-patterns
* People wanted them still, so they said "fine, pay us to port it". Or, do it yourself - the MIT code is sitting right there and the changes are not all that significant. You'd also learn more about D* while at it. I linked in the parent comment to the MIT code - would not be difficult for anyone to do.
I suspect that in the long-run (probably not too far from now), they'll just make those plugins MIT again as the real value of Pro is the inspector, and soon their WIP web component framework (Rocket) and css framework (stellar) - all of which have always been being a commercial license.
p.s. there's no techcorp here. Its literally 3 guys with day jobs donating their time to a 501c3-registered non-profit. Funds go to things like going to conferences, or holding their own.
I'm not qualified to comment as I don't use the feature, but people in their Discord would be happy to explain/discuss - its a wonderful place for learning more about the web, backend architecture and more.
I'm also sure this has already been explained in comments to other posts here as well.
It hasn't. Also, I'm definitely not going into the Datastar Discord. The main guy gets incredibly agitated and starts calling you names if you don't agree with him about anything lol
> 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
It seems odd to me to treat these as different properties on their own? might as well keep it as an extension if it is not going to be more deeply integrated and let htmx 4.0 be smaller? Possible I may not be understanding the use case though
Yep, it's super nice. The Service Workers API also makes this really easy too. I experimented with compiling a Rust Axum server to WASM and then ran it in my service worker. Also, thanks for incorporating fetch into htmx!
I thought I'd include an example of replacing fetch for anyone that come across this.
> So, eventually & reluctantly, I have changed my mind: there will be another major version of htmx.
So first you weren't going to make a new major version, because htmx was sooo perfect, but now you had realized how much it can be improved.
Obviously, all software needs to evolve, and it was always very silly to say "this is the final major version". Why would someone use software from such kind of developer is beyond my understanding. But of course I also don't understand anything about this library; this surely must be some kind of trolling:
> We are going to adopt a new standard for event naming to make things even clearer:
> htmx:<phase>:<system>[:<optional-sub-action>]
It's truly wonderful what can people do to avoid writing JavaScript :D
yes, you write this weird custom syntax that's interpreted in js, without writing any single line of js!
well, except when you want to do drag and drop sorting and this other thing.
yeah you get to communicate intent with html, but ignoring the security concerns for arguments sake, an inline script tag or your good old onclick event handler can do that too.
Interesting timing - I'm building genx.software which leans hard into implicit inheritance for the exact reason he is abandoning it. In my finance app, when the product owner changes their mind about decimal places for the 47th time (and it's always 47), the last thing I want is to hunt down every cell and add :inherited modifiers.
We treat everything as integers server-side (because floating point is the devil) and defer all formatting to the client. Implicit cascade means I can change fmt-x="currency:USD:decimals:2" in one place and watch it ripple down the entire table. It's 'maddening' the way CSS is maddening - which is to say, it becomes second nature after you stop fighting it.
That said, I fully understand the support ticket burden. Maybe the real lesson is: implicit inheritance is great when you control the domain (like 'format all money the same way'), but terrible when people want to do arbitrary things at arbitrary levels. I will bear that in mind as I complete genx.software
The only big gripe I have about htmx is that the hx-on::after-request response it provides to your callback function does not automatically parse JSON content types like with e.g. jQuery.ajax(). Last time I brought that up, people simply questioned why I would ever want to do that in the first place.
One of the actual responses was "Htmx isn’t designed to work with JSON APIs at all. It needs HTML back from the server."
It sounds like they are referring to hx-swap and not arbitrary javascript callbacks though, and in that case, I don't see why calling JSON.parse() inside htmx (when the content-type is json) is that big of a deal.
They don't want to become a general purpose dynamic web library, but focus on swapping server generated HTML blocks. It's a conscious decision in what they are and what not.
> 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.
technically correct.. the best kind of correct
It's an amusing solution, but if this ends up being anything like the missing PHP 6, it's also going to cause confusion for users. It might've been better to just mea culpa and release 3.0 anyway. I can't imagine anybody would really hold it against the author.
As long as they commit to making the next version after this htmx 8, I will be content with this versioning scheme.
Why skip version 7? It should be 6 7
Then 8
Meme versioning could work. Memes are temporal.
You can derive a lot of information about my age and current version from the dad-joke version string “I thought it was 6 7 when 7 8 9”
Maybe the versioning should be powers of 2
def 6 and 7
if there is another version of htmx it will be 8 for sure
htmx forever
Apple just jumped to version 26 across all its code bases. I think HTMX can survive with skipping to 4.
What's the confusion around PHP 6? - There would have been a lot more confusion due to articles, talk slides, even printed books, .. talking about PHP 6's Unicode support would have been truly confusing.
A mea culpa would've been just fine. Even Big Tech can't keep their promises around this stuff (Windows 10 was the last OS remember?)
Maybe they could just initially launch v3.1 instead of a v3.0. They would still be technically correct.
Traditionally, software engineers are smart people
Source for this? After years in the industry, I believe otherwise at this point.
There are plenty of people with high INT and low WIS. As the old joke goes, intelligence is knowing that a tomato is a fruit. Wisdom is knowing not to put tomatoes in a fruit salad.
INT is knowing how to use Kubernetes, WIS is knowing when not to?
I wonder if the intersection of HTMX and Kubernetes users is empty or not :). I wouldn't be surprised (and it'd be great!) if it is.
Should go directly to htmx 4.1, so we can finally have xhtmx 1.0
Got a https://en.wikipedia.org/wiki/Leisure_Suit_Larry#Leisure_Sui... vibe from it.
Spaceballs 3: The Search For Spaceballs 2: The Search For More Money
The author is lucky the phrasing wasn't "there won't be another major version of htmx", or even "a third version".
From what I know of Carson from his writing and presentations, he probably worded it that way on purpose knowing he'd eventually do a new version, and he didn't want to miss an opportunity to troll everyone a bit.
> htmx 2.0 (like htmx 1.0 & intercooler.js 1.0) will be supported in perpetuity, so there is absolutely no pressure to upgrade your application: if htmx 2.0 is satisfying your hypermedia needs, you can stick with it
This is commendable, specially during times when libraries and programs aren't afraid of breaking changes and API churn.
I think the ideas behind HTMX are cool, and Carson’s advocacy around Hypermedia is great.
I came from SPA-land and was tired of the fundamental architectural issue of having to keep the front-end and back-end state in sync.
I’ve compared Datastar and HTMX and decided on Datastar. There’s overlap between the two libraries in that they both support the request/response model, but with Datastar my learning investment takes me further and opens up new possibilities.
In one project I was able to remove a bunch of polling code and simply push a signal from the server to the browser when an external event occurred. The reduction in complexity was crazy.
On an internal tool I noticed I didn’t need Alpine.js anymore, and while anytime I can remove a dependency is a good time, the conceptual simplicity is what really makes me happy.
Now I’m doing a small app where I decided to make a streaming connection between browser and server and simply regenerate and send the entire view over that connection anytime something changes. Intuitively this felt wasteful but it turned out that with compression this works beautifully. There’s just less code in my app, and the code that’s there is less “fiddly” not having to deal with partial updates of the page.
If you’re coming from the world of SPAs, definitely check out both.
Datastar looks interesting, but their guide on how to redirect a user is a joke.
async def redirect_from_backend():
LOL, they really think it's a good use of server resources to sleep so the client doesn't have to.[1] https://data-star.dev/how_tos/redirect_the_page_from_the_bac...
One of the things I had to wrap my head around was the whole async server style of programming. I learned that doing a sleep on the server consumes essentially zero resources. The thread goes to sleep and wakes up when the timer expires. In the meantime the server can handle other requests unhindered. You can have hundreds or thousands of these threads at the same time - serving requests, waiting for IO, etc.
It’s not like synchronous servers e.g. Flask with Gunicorn (in the Python world) where you have a few pre-forked server processes. In that case it would be a waste to prevent one of those processes from serving other requests while sleeping, I agree.
Ultimately it’s cool though to be able to control the client from the backend asynchronously, updating signals (think of them as a kind of special front-end variable with superpowers) when something on the backend happens, or updating the client’s DOM, or running scripts.
Sleeping on the server still consumes resources like memory, open sockets, etc, which may in turn cause some unnecessary CPU usage because of e.g. GC costs, scheduling costs, etc.
To a point, but sleeping before redirecting on the server is insanity. It's pointlessly brittle when the client is perfectly capable of managing its own timer.
This approach completely breaks sensible reasoning about what clients are responsible for. What's next, sending every keystroke to the server so it can tell the client to update a text input?!?
It still takes resources to keep SSEs open, load balance, reopen lost connections, etc. But worst of all is shifting fundamental logic back to the server for literally no benefit.
It's a toy example, it's suppose to show that you can be doing N things and then redirect. If you have a normal redirect immediately, just do a 307 or whatever.
> 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.
So confusing. I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
UPDATE: or "inherit", sounds like a command, little less confusing.
I was going to make the same comment. "inherited" is the wrong word. I like "inherit" too. And it's a bit shorter which is always nice. I guess "bequeath" might work also?
> I'm pretty sure it should be "inheritable", because "inherited" on an attribute means the attribute is inherited, not the element's children will inherit the attribute.
That is one interpretation, but not the one I had reading it. I read it as "This target will be inherited by its children," rather than "This target is inherited from its parents," which, while a grammatical possibility, doesn't really make sense because you are specifying it right there with the equal sign.
Both "inherit" and "inheritable" have the same possible double readings. Something like "pass-to-children" or "pass-down" would remove the possible ambiguity, but I'm not sure like either better than "inherited".
yeah, i'm struggling w/it, willing to consider options
“curse”
Like male pattern baldness, we are cursed by our parents to inherit it
If you're ok with an imperative verb, "propagate" could fit.
Heirloom
Maybe `heritable`?
or 'cascade' ?
Well, I just remembered that in SQL "ON UPDATE CASCADE" is specified on downstream keys (children), not on their target (parent).
I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
This feels like a repeat of the Python 3.0 strategy, though obviously at a much smaller scale. Some stuff is of course hard to roll out but to me it feels "obvious" that having a 4.0 with the inheritence change (or even better, a 2.1 with a toggle on that change!), then a 5.0 with some other changes, then a 6.0 with other changes... all feels way easier to manage than a 4.0 with all the changes at once.
We have version pinning! People who want 2.0 can have 2.0 "forever", so version numbers that go up very high are not actually a problem. Many releases can of course be a bit of a frustration from a release maker's perspective, but given that htmx is the way it is (ain't even getting type checking helping you out on any of this like you would with React!), having the gradual path seems way better.
"I think I've handled the 10 changes in between 2.0 and 4.0... but forgot the 11th change" is a constant annoyance with these huge major version bumps.
I will once again point to the Django strategy of having breaking changes be rolled out over several releases, including in between releases where "both" models exist. It is a very nice way to do release management when rolling things out, gives good checkpoints for pushing things out, and overall means users are seeing less bugs. Going from `XMLHttpRequest` to `fetch` really might not be a feasable thing to toggle, but a lot of the other stuff in that list feels like it.
>I feel like people who crave stability pull all the wrong lessons from how to get to stability when doing this sort of "ok we'll make _one_ major version with all the breaking changes we've built up over time".
Yeah this is a huge fallacy. If you do a breaking change, you want to do as few breaking changes at once as possible, with as little impact as possible and in the best case scenario you're in contact with every single person that is affected by the breaking change, so that it can be handled without anyone noticing anything other than having to bump a library version, which they were doing anyway since they're running a new Python version in the first place.
The dumbest change in python 3 was removing the print statement [0]. Maximum breakage, minimum gain. They could have introduced the new function as println() and deprecated the print statement in Python 3.
[0] https://news.ycombinator.com/item?id=13260563
> 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.
Based on this section, it will be interesting to see how this evolves. I've used HTMX a bunch but after stumbling on Datastar I've come to prefer it. Partially because I don't need something like alpine.js to get some frontend goodies but also because I've come to appreciate the power of SSE streaming morphable targets to the browser
Great to see this evolution! Realtime ssr hypermedia is definitely the future.
But, my thoughts immediately go to Datastar, which has Fetch, SSE, declarative signals and js expressions, dom morphing, and much more - in a tiny package. I find it to have a more flexible, expressive and standards-compliant API as well. And it'll soon have a simple reactive web components and css framework as well.
At this point, why use HTMX when it really seems like (a heavier) Datastar-lite?
Because Datastar Pro isn't FOSS, and speaking from a purely probabilistic and historical standpoint, your odds of getting rugpulled (in some form or another) might as well be 100%.
I can appreciate why people would have this perspective - I have been rugpulled a few times recently by thoroughly unscrupulous companies (Augment Code are trash humans).
But Datastar is different. The project is literally owned by a 501c3 non-profit. The devs have dayjobs and donate their spare time to this. Funds are for going to conferences or hosting their own
And 99% of the features/value that I mentioned is MIT licensed, and the "rugpulled" code is still available to easily port via the plugin API.
And OpenAI was a non-profit. "Was" being the operative word.
Ah yes, one of the world's most valuable companies is totally a solid parallel for a few guys part-time making a simple, niche js framework whose pro license they discourage people from purchasing... Totally.
You don't know that, even if they were your brothers sitting in the room with you right now you don't know if they are or not.
How does this differ from any other open source project (most of which are not non-profits, and are utterly unsustainable)?
As much as I like Delaney, the discord server and Datastar I find this question baffling.
If the Postgres team released PGPro, swore it just contained anti patterns and you can just write the code yourself if you needed that feature, you’d roll your eyes, no?
It’s about the fact they went there, not about the intentions.
You are right! If this is enough to put you off the project then I hope you find one that aligns with your ideals.
Yeah HTMX should rebrand as DATASTAR LIBRE.
Presumably Datastar is a trademark that shouldn't be used that way? (Or does the owner of HTMX also own Datastar?)
It's a joke.
I dont think they have a trademark. I also think they'd be completely in favour of HTMX rebranding to Datastar Libre.
As the Datastar author, I'd approve this effort
> why use HTMX when it really seems like (a heavier) Datastar-lite?
The reason to use htmx is that it has a simpler interface optimized for the majority use-case.
With htmx, you are largely tied to a request/reply paradigm. Something happens that triggers a request (e.g. user clicks a button, or some element scrolls into view), htmx sends the request, and then it processes the response. The htmx interface (`hx-get`, hx-trigger`) is optimized to make this paradigm extremely simple and concise to specify.
Datastar's focus (last I checked) is on decoupling these two things. Events may stream to the client at any time, regardless of whether or not they were triggered by a specific action on the client, and they get processed by Datastar and have some effect on the page. htmx has affordances for listening to events (SEE extension, new fetch support) and for placing items arbitrarily on the page (out-of-band swaps) but if your use-case is a video game or a dashboard or something else where the updates are frequently uncorrelated with user actions, Datastar makes a lot of sense. It's a bit like driving a manual transmission.
Delaney is fond of saying that there's no need for htmx when Datastar can technically do everything htmx can [0]. But I think this misses the point of what makes htmx so popular: most people's applications do fit within a largely request/reply paradigm, and using a library that assumes this paradigm is both simpler to implement and simpler to debug. As an htmx maintainer, I often encourage people to even use htmx less than they want to, because the request/reply paradigm is very powerful and the more you can adhere to browser's understanding of it, the more durable and maintainable your website will be [1].
[0] https://data-star.dev/essays/v1_and_beyond
[1] https://unplannedobsolescence.com/blog/less-htmx-is-more/
Respectfully, almost everything you said is just plain wrong.
1. Datastar supports req/reply just fine - be it via normal text/html responses, or SSE (0, 1, or infinity responses) https://data-star.dev/reference/actions#response-handling. So, the crux of your argument is moot...
Moreover, if htmx's real value is ajax request/response, then why are you introducing SSE as a first-class citizen now?
2. Datastar has data-on, and various other attributes, that allow for triggering far more actions than just backend requests, from far more (any) events. I'm glad to see that htmx is now following suit with hx-on, even if it is apparently limited in capabilities.
3. Datastar can do OOB-swaps just fine - that's literally the core functionality, via (their own, faster) idiomorph.
4. Its a misnomer that Datastar is for video games etc - again, as described above, it can do all of the simple things that that HTMX can do, and more. And, again, why is HTMX introducing SSE if its so apparently unnecessary and unwieldy?
5. What makes htmx popular is that it was the first library to make declarative fragment swapping easy. And Carson is just a god-tier marketer. Its nice to see that he's now realized that Delaney was on to something when he wanted to introduce all of these v4 features to HTMX 3 years ago, but was (fortunately for us happy users) forced to go make Datastar instead.
6. We havent even talked about one of the key features - declarative signals. Signals are justifiably taking over all of the JS frameworks and there's even an active proposal to make them part of the web platform. D* makes them simpler to use than any of them, and in a tiny package.
I, Delaney, and all other D* users are grateful for HTMX opening this door. But I reiterate my original question - now that HTMX is becoming Datastar-lite, why not just use Datastar given that the powerful extras don't add any complexity and comes in a smaller package?
So here's the htmx example for click to edit: [0]
<button hx-get="/contact/1/edit">
And here's the datastar one, edited for parity: [1]
<button data-on:click="@get('/contact/1/edit')">
The htmx one is simpler. There's fewer mini-languages to learn and the API makes more assumptions about what you want. As you noted, Datastar has more generalized mechanisms that are certainly less clunky than htmx's if you lean heavily into more signals- or event-driven behavior, but for (what I believe to be) the majority use-case of a CRUD website, htmx's simpler interface is easier to implement and debug.(For example: you will see the response associated with the request in the browser network tab; I'm not sure if Datastar has a non-SSE mode to support that but it wouldn't be true for SSE.) To each their own.
As for "well then why implement X, Y, or Z," as the OP notes, refactoring to use fetch() means you get them largely for free, without compromising the nice interface. So why not?
[0] https://htmx.org/examples/click-to-edit/
[1] https://data-star.dev/examples/click_to_edit
That tiny difference is hardly a reason not to use datastar, especially when it brings SO MUCH more useful stuff - all in a smaller package.
Moreover, the fact that Datastar is more generalized is actually better - HTMX has vastly more (non-standards-compliant) attributes that you need to learn.
https://htmx.org/reference/
vs
https://data-star.dev/reference/attributes
> I'm not sure if Datastar has a non-SSE mode to support that but it wouldn't be true for SSE.) To each their own.
My first point was literally saying that it has non-SSE and linked to the docs. You're not even trying to be objective here...
> So why not?
Yes, I have no problem with these things being implemented in v4. In fact, celebrated it in my original post. I brought it all up because you were describing that all as needless complexity in Datastar, but now you're implementing it.
Also, most of Datastar can be trivially disabled/unbundled because its nearly all plugins. That is largely not the case for HTMX.
Thus far, you've simply strongly confirmed my initial hunch that HTMX v4 is unnecessary compared to Datastar.
Less characters is not simple that's easy.
Which is why HTMX is having to bolt on more gubbins. Because, although it's less characters to type its fundamentally complected and therefore less composable.
I'm sure you've already warched it but if you haven't I'd recommend Rich Hickey's talk Simple made Easy.
"Their own faster idiomoroh"
HTMX's 4's morph is almost a copy paste of Datastar's.
To be fair, the idiomorph work came from Datastar and Turbo community efforts. There is a whole podcast about Micah https://www.youtube.com/watch?v=IrtBBqyDrJU&pp=ygUOZGF0YXN0Y.... Latent has been a huge force behind these ideas as well!
Why bother with v4 at all? If it dilutes that simpler interface?
I think that even with req/resp morph leads to a simpler majority use case and that's what Turbo and Datastar have both shown. No?
> Why bother with v4 at all? If it dilutes that simpler interface?
v4 makes almost no changes to the interface, other than to flip inheritance to be off by default.
> I think that even with req/resp morph leads to a simpler majority use case and that's what Turbo and Datastar have both shown. No?
Although you can use the idiomorph extension for htmx, I personally don't think idiomorph is simpler, because there's an algorithm choosing what parts of the page get replaced based on the server response; I prefer to specify exactly what parts of the page get replaced in much simpler terms, a CSS selector, with `hx-target`.
Per [1] above, my style is minimize partial page responses wherever possible, so the ones that I do have are bespoke and replace a specific thing.
Your personal preferences aside. Full page morphs get you back to 3XX redirects of pure HTML (no JS forms) when you use them with req/resp.
https://dev.37signals.com/a-happier-happy-path-in-turbo-with...
Are you aware that v4 is baking idiomorph into the core...?
Yes! I expect that I will mostly be sticking to `hx-target` though, for the reasons stated above.
My interest in htmx is more on the coarse-grained aspects of its interface, not the finer ones, which is a consistent theme in my writings about it [0].
[0] https://alexanderpetros.com/triptych/
hey Alex, I hope you are well. Datastar has had direct support for req/rep of HTML, JS, JSON while still morphing for a quite a while. They allow you to go as coarse as you want. Give the size and ability to choose what plugins you actually need seems like Datastar is more in line with your wants at this point. Strange times.
It's actually baking in datastar morph. Which is even better.
The irony that the Datastar author originally tried to have those features added to HTMX a few years ago.
Better late than never. Everyone benefits with HTMX evolving and bringing more attention and capabilities to hypermedia-first approaches.
One important difference that I found is that HTMX (and Alpine AJAX) can easily push a URL into the browser's location history. I've used this feature often with my Django projects--essentially storing the state in the URL which is great for sharing URLs or bookmarking them. As far as I'm aware, Datastar have locked this feature behind the "Pro" paywall.
Fair. Though, D*'s authors are pretty adamant that this is an anti-pattern - that's why they put it behind the paywall.
Moreover, the FOSS code still exists and would take 2 minutes to update to the current plugin API (I have Datastar pro and the code is almost exactly the same)
https://github.com/starfederation/datastar/blob/v1.0.0-beta....
> D*'s authors are pretty adamant that this is an anti-pattern
The Datastar authors are wrong about this. History push is a very important part of the hypermedia-driven application approach. Because URLs are super important. And we want to make sure that the correct URL is shown for the currently-loaded view, and that the view is reproducible given the URL (as much as possible) so that bookmarking and copy-pasting to send URLs just works as expected.
A really nice article came out about this just recently: https://alfy.blog/2025/10/31/your-url-is-your-state.html
I also wrote a bit more about it here: https://dev.to/yawaramin/why-hx-boost-is-actually-the-most-i...
Thanks very much for a thoughtful reply and link to your article. I look forward to reading it.
As it turns out, I shared that very same article in the Datastar discord the other day! Here's some other good ones that I found while digging into the topic, for anyone who cares.
* https://warpspire.com/posts/url-design/
* https://blog.jim-nielsen.com/2023/examples-of-great-urls/
* https://www.w3.org/Provider/Style/URI
* https://www.hanselman.com/blog/urls-are-ui
I strongly agree that good urls are very important. But I don't see how D* prevents correct urls/history at all... You can click anchor links just fine for pages that are genuinely separate pages. If its just a sub page, filter etc, then i think in many cases it should only swap into the dom without updating the history.
Moreover, am I wrong to think that if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark? That wouldn't happen with non-boosted anchor links.
Anyway, I'm not the best person to take up this argument. If you are interested at all in some respectful debate on the topic, it would be great if you came by the datastar discord where there's definitely people who would be better able to engage with it. I'd be eager to observe from the sidelines
"Moreover, am I wrong to think that if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark? That wouldn't happen with non-boosted anchor links."
It's a common pattern with Django and template partials that you check if the request is an AJAX request, in which case you just load a partial template to swap into the existing DOM. Or if it's not an AJAX request, your server-side logic loads the full template.
A simple example would be a to-do list at http://example.com/todo/. Clicking on a task item would swap it into the DOM without a full page load, and then you'd update the URL and browser history to http://example.com/todo/my-task/. Then if you open that URL in a new session, your server side logic would render your page with the "my-task" already selected.
> If its just a sub page, filter etc, then i think in many cases it should only swap into the dom without updating the history.
It depends. If it's a 'resource' (in the REST sense) then it should actually push the URL of the resource into history, because the URL should correspond to the currently viewed resource. This is exactly what I was talking about earlier, it's super important as a basic hypermedia principle.
> if you use hx-boost to swap in fragments, then the URL that gets updated/saved in history wouldn't actually load the same page if you loaded it from a bookmark
See amanzi's explanation or my blog post where I explain the same thing. With htmx we can easily check for the presence of a request header in the backend and serve the appropriate version of the resource: either a partial (fragment) rendering or a full page that contains the resource. I highly recommend reading my blog post: it's not a huge commitment and it will clarify these issues for you.
Pay for the antipatterns?
I'm too tired to parse this logic, but I suspect it is a novel entry in techcorp doublespeak/dirty tricks.
I think the logic is the following:
* Datastar was re-written from the ground up, numerous times.
* They didn't want to update and maintain the plugins that they viewed as unnecessary/anti-patterns
* People wanted them still, so they said "fine, pay us to port it". Or, do it yourself - the MIT code is sitting right there and the changes are not all that significant. You'd also learn more about D* while at it. I linked in the parent comment to the MIT code - would not be difficult for anyone to do.
I suspect that in the long-run (probably not too far from now), they'll just make those plugins MIT again as the real value of Pro is the inspector, and soon their WIP web component framework (Rocket) and css framework (stellar) - all of which have always been being a commercial license.
p.s. there's no techcorp here. Its literally 3 guys with day jobs donating their time to a 501c3-registered non-profit. Funds go to things like going to conferences, or holding their own.
> the real value of Pro is the inspector
Ah, yes, a debugging tool. Only professionals need those.
I can't tell what sort of sarcasm this is - whether youre saying that the inspector should be available to all, or that no one actually needs it.
Whatever the case, you dont truly need it, but it is helpful. You buy it for convenience as well as to support the project.
You just sound like a marketer or a cultist, and I'm not sure which one is worse.
Its worse than that - I'm a cult marketer/evangelist!
I'm interested in why this would be an anti-pattern? What would the alternative be?
I'm not qualified to comment as I don't use the feature, but people in their Discord would be happy to explain/discuss - its a wonderful place for learning more about the web, backend architecture and more.
I'm also sure this has already been explained in comments to other posts here as well.
It hasn't. Also, I'm definitely not going into the Datastar Discord. The main guy gets incredibly agitated and starts calling you names if you don't agree with him about anything lol
Check out window.history.pushState
The whole point of these frameworks is to not have to write JavaScript.
Not something I'd pay a premium for.
> 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
It seems odd to me to treat these as different properties on their own? might as well keep it as an extension if it is not going to be more deeply integrated and let htmx 4.0 be smaller? Possible I may not be understanding the use case though
I really enjoyed reading this - very clear technical writing and I learned quite a bit about htmx and their approach to API design generally from it.
Computer works in binary
htmx 1 htmx 2 htmx 4
I look forward to version 8 in 2030.
(Meaning there always was a version 3! Just include both htmx1 and htmx2 onto your webpage!)
This is great! I had to create an xhr-fetch-proxy to use fetch and htmx. This change should open up a lot of fun possibilities.
https://github.com/logankeenan/xhr-fetch-proxy
in 4.0 the request cycle is totally open: you can replace the fetch() implementation on a per-request basis
learned that trick from fixi.js
Yep, it's super nice. The Service Workers API also makes this really easy too. I experimented with compiling a Rust Axum server to WASM and then ran it in my service worker. Also, thanks for incorporating fetch into htmx!
I thought I'd include an example of replacing fetch for anyone that come across this.
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWork...in htmx 4 you are able to swap it on a per-trigger basis, so need to muck w/the global fetch function:
Oh that’ll be much nicer. Would myCustomFetch then need to return a Response?
https://developer.mozilla.org/en-US/docs/Web/API/Response
> So, eventually & reluctantly, I have changed my mind: there will be another major version of htmx.
So first you weren't going to make a new major version, because htmx was sooo perfect, but now you had realized how much it can be improved.
Obviously, all software needs to evolve, and it was always very silly to say "this is the final major version". Why would someone use software from such kind of developer is beyond my understanding. But of course I also don't understand anything about this library; this surely must be some kind of trolling:
> We are going to adopt a new standard for event naming to make things even clearer:
> htmx:<phase>:<system>[:<optional-sub-action>]
It's truly wonderful what can people do to avoid writing JavaScript :D
yes, you write this weird custom syntax that's interpreted in js, without writing any single line of js!
well, except when you want to do drag and drop sorting and this other thing.
yeah you get to communicate intent with html, but ignoring the security concerns for arguments sake, an inline script tag or your good old onclick event handler can do that too.
Declarative style is almost always a win, IMO.
this one is really, truly the last version for sure
Excited for Htmy!
htmxkcd
it's truly wonderful how much can be done and still be better than writing javascript
Do you actually feel like this is better (and not just at par or worse)?
Almost anything is better than writing JavaScript
I find it much better for my use cases to use this than to use a JavaScript framework the necessitates the use of a JavaScript server
I feel like Carson Gross wrote this himself lol
The only trolling I see is in your tone.
Okay, the author changed idea, so?
What's your point?
Looking forward to porting my demos [1] from Datastar to HTMX once V4 is stable enough. Exciting stuff. Nice to see HTMX innovating again.
- [1] https://checkboxes.andersmurphy.com/
Interesting timing - I'm building genx.software which leans hard into implicit inheritance for the exact reason he is abandoning it. In my finance app, when the product owner changes their mind about decimal places for the 47th time (and it's always 47), the last thing I want is to hunt down every cell and add :inherited modifiers.
We treat everything as integers server-side (because floating point is the devil) and defer all formatting to the client. Implicit cascade means I can change fmt-x="currency:USD:decimals:2" in one place and watch it ripple down the entire table. It's 'maddening' the way CSS is maddening - which is to say, it becomes second nature after you stop fighting it.
That said, I fully understand the support ticket burden. Maybe the real lesson is: implicit inheritance is great when you control the domain (like 'format all money the same way'), but terrible when people want to do arbitrary things at arbitrary levels. I will bear that in mind as I complete genx.software
This looks great, although the version 4 alpha doesn't appear to be available at the suggested URL: https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-alpha/dist/htmx....
"Couldn't find the requested release version 4.0.0-alpha."
It's named 4.0.0-alpha1. Just add the 1 and the link works
Thanks. The docs need to be updated at https://four.htmx.org/
htmx hasn’t had the piss taken out of it enough.
100%
Neato. Sensible updates, no loss of support for previous versions? The software dream.
The image breaks the website on mobile for me.
The only big gripe I have about htmx is that the hx-on::after-request response it provides to your callback function does not automatically parse JSON content types like with e.g. jQuery.ajax(). Last time I brought that up, people simply questioned why I would ever want to do that in the first place.
That sounds like use-case exploration? Did you answer?
One of the actual responses was "Htmx isn’t designed to work with JSON APIs at all. It needs HTML back from the server."
It sounds like they are referring to hx-swap and not arbitrary javascript callbacks though, and in that case, I don't see why calling JSON.parse() inside htmx (when the content-type is json) is that big of a deal.
It's a pretty core part of their design philosophy, possible the core.
https://htmx.org/essays/rest-explained/
They don't want to become a general purpose dynamic web library, but focus on swapping server generated HTML blocks. It's a conscious decision in what they are and what not.
> One of the actual responses was "Htmx isn’t designed to work with JSON APIs at all. It needs HTML back from the server."
Uh, yes? They wrote a literal book about why they think this is important: https://hypermedia.systems/
in 4.0 we are opening up the entire request/response/swap mechanism so you can replace any component of it per-trigger
you can replace the fetch() function used w/ an event callback, etc
should allow you to do pretty much anything w/o any hacks
I was just about to use this for a feature but i won't bother now, it won't pass the stability test. Thanks I guess.