These kinds of write-ups are so key to driving adoption of a new technology. I'm still not super interested in HTMX but this write-up has done a lot of the work already toward nudging me that way. Well done!
I read through this and I don't get it. Recreating an entire form on the backend and swapping it with the current one, and then missing the update of the label status?
Then solving this by recreating the entire stepper html at each step, with the added complexity that if it contains something you want to keep "it's a nightmare"?
Then having to create a temporary server-side session to store data that somehow the browser can't keep between two clicks?
> I originally planned to make a simple non-functional uploader, then progressively bring it from "it works™" to "high-quality production-grade Uploader with S3 Presigned, Multipart, Accelerated Uploads, along with Auto-Saving and Non-Linear Navigation
Why is every developer trying to make things complicated?
This implementation is unnecessary complicated. For the step update, you can use Out Of Band update: https://htmx.org/attributes/hx-swap-oob/ which works in a way that you can send multiple HTML fragments which can be anywhere on the page and HTML swaps them out. Good for notifications, step update, breadcrumb update, menu highlight, etc...
I usually solve the second problem by simply saving the state of the individual input fields, you only need a user session.
Depending your use-case, you might need to be transactional, but you can still do this saving everything as "partial" and close the "transaction" (whatever it might mean in the given context) at the last step. Much-much simpler than sending form data over and over.
Every time I attempt to use HTMX and backend-rendered templates because it's "simpler", in the end I always end up doing JSON APIs and something like Svelte. Because all the "simplicity" explodes in complexity 5 seconds later, and it's very user-hostile with the constant reloads.
I built my latest SaaS (https://clarohq.com) using HTMX, backed by Django. I really enjoy the process because HTMX allows reactivity using swaps and plain Javascript events instead of server side state management, useeffect, and API endpoints.
However, it's difficult to get things right. I spent way too much time on some basic features that I could have shipped quicker if I used React.
The issue with React though, is that you end up with a ton of dependencies, which makes your app harder to maintain in the long-term. For example, I have to use a third-party library called react-hook-form to build forms, when I can do the same thing using plain HTML and a few AlpineJS directives if I need dynamic fields.
I'm not sure if I'll ever build an app using HTMX again but we need more people to write about it so that we can nail down patterns for quickly building server rendered reactive UIs.
I want to make the intent of this blog post extremely clear (which tragically got lost when I got deep into the writing).
I love HTMX, and I've built entire sites around it. But all over the internet, I've seen HTMX praised as this pristine perfect one-stop-solution that makes all problems simple & easy (or at least... easier than any framework could ever do).
This is a sentiment I have not found to be true in my work, and even one where the author of HTMX spoke out against (although I can't find the link :(
It's not a bad solution (it's actually a very good solution), but in real production sites, you will find yourself scratching your head sometimes. For most applications, I believe it will make ALMOST everything simpler (and lighter) than traditional SPA frameworks.
But for some "parts" of it, it is a little tricker, do read "When Should You Use Hypermedia?" [1];
In the next blog post (where we'll be implementing the "REAL" killer features), I hope to demonstrate that "yes, HTMX can do this, but it's not all sunshine & rainbows."
---
On a completely separate note, one may ask, then, "why use HTMX?" Personally, for me, it's not even about the features of HTMX. It's actually all about rendering HTML in the backend with something like Templ [2] (or any type-safe html templating language).
With Templ (or any type-safe templating language), I get to render UI from the server in a type-safe language (Golang) accessing properties that I KNOW exist in my data model. As in, the application literally won't compile & run if I reference a property in the UI that doesn't exist or is of the incorrect type.
You don't get that with a middle-man API communication layer maintained between frontend and backend.
All I need now is reactivity, and htmx was the answer. Hope you understand!
Looking at the solution you ended up with, I feel like it's actually fairly reasonable in terms of implementation complexity compared to the feature it is delivering. I have a hard time believing that a pure SPA approach would be simpler to implement. Certainly a SPA would deliver a much more bloated JS payload to the client, which really doesn't seem like a good tradeoff for basically filling out a form and uploading a couple of files.
Yes, going from SPA-like libs to hyper text libs is definitely a change in how you use your mind and approach problems. It's like going from OO to Functional-style programming. It really messes with the brain. But eventually you learn to love both paradigms and see the good and both and then you start using both patterns in your code, which really messes with other devs as they see both patterns being used.
I've been programming on personal projects with html-form (my own lib that is a radically paired down version of HTMX with a focus on using native form controls, which makes it so you don't need hydration like you do in HTMX).
So, some thoughts on your problem.
One note. Many people mention OOB. But there is also hx-select, which can be quite handy.
I think there are multiple different approaches that are possible, that I can think of, depending on your needs.
Use eventing. On the server create an event that will be called on the front end by HTMX. When this is called have some vanilla JS that updates the class on your location in the form.
Send all the forms down and have them only visible based on CSS or using an input element is checked or not or using JS. That would be pretty straight forward.
If the form passes then replace all the body contents or main element. If it doesn't just update the form and add any user information. Or send an event back which causes a popup to tell the user what their issue is. Or send an even that causes some text to show.
Also, you can also use CSS and native HTML form validation that will show text underneath an input (or above the input) that tells the user what the the input requires. Modern CSS has a pseudo selector which only turns on when the input is invalid[1].
This is simple enough that a straight up MPA could be used with page animations between pages using view transitions[2].
Similar to one of the solutions above, but using data-action style of programming.[3]
I've found for state machine on the front end which uses mostly data that is already on the front end that using a lib like VanJS can be very nice and useful. Using the correct paradigm for the problem is nice. This would be over kill for the problem mentioned in this post though.
I’ve attempted to use HTMX a few times (as a React-hater, its hype lures me in) and every time I’ve come away feeling like I’ve wasted my time implementing a subpar solution.
From reading this, I’ve decided I will never attempt to use it again. All I could think was, just use Go’s HTML templating. What is HTMX adding, really?
I enjoyed reading this. It follows a similar experience with our first htmx website, away from using modern frontends, or just simple jQuery with ajax json data.
I remember, working with a co-worker, we planned out the process (a step-by-step on the application like this post) and it made sense to me - but this journey was much harder for my co-worker.
Why is this? Simply because he is familiar with MVC pattern of sending json data back and forth, and getting the frontend to update and render it, etc. The idea of html flying with htmx as the behaviour (inside html tags) was just too much.
For me, I always preferred the old school way of just letting the server-side generate the html. All htmx does is adds extra functionality.
I tried hard to explain that we are sending html back, and to break things down one at a time but each new task was left scratching his head.
In the end, our website had somewhere around 20-50 lines of javascript! Much smaller footprint than over 400 lines in our previous project (that being generous). Sure, our server side code was larger, but it was all organised into View/PartalView files. To me, it made really good sense.
In the end, I dont think I won htmx over with my co-worker. As for another co-worker, who had a chance to build a new project with htmx, decided on some client javascript tool instead. I dont think I got a legit answer why he did that.
With all this above, I learned that some (perhaps most... perhaps all) struggle to adapt to htmx based on their many years building websites a particular way, with popular tools and javascript libraries. Overall htmx does not really change anything - you are still building a website. If anything htmx just add an additional layer to have website really work.
Yoda's words now have new meaning :-
"No! No different. Only different in your mind. You must unlearn what you have learned."
For some its just not happening. I guess Luke Skywalker really shows his willpower, being able to adapt to htmx easier than others. :-)
HTMX is amazing for simpler web apps. If you have a ton of complexity, need to manage a lot of state, etc., I can see how it would be frustrating trying to get everything to fit into HTMX's patterns. In fact, it might actually increase complexity. But, if you have something smaller and want to make it more interactive, React is way overkill and HTMX is a breath of fresh air.
I think a lot of the arguments over HTMX come down to this difference. The people that love it see how much better it is for their use case than something like React, while the critics are finding it can't replace bigger frameworks for more demanding projects.
(Here's an example interface made with HTMX. IMO React would have been overkill for this compared to how simple it was with HTMX. https://www.bulletyn.co )
I think you need to make peace with OOB if you want to enjoy working with HTMX. You need to have a framework such that you can draw a partial inside the page when you send the whole HTML document but also render and draw a group of partials to update several things that change with a request.
return pox.Templ(http.StatusOK, templates.AlertError("Name cannot be empty")), nil
Oof, an HTTP 200 OK response with a body that says the request actually was not OK.
I like htmx, but this is probably the weakest part of it.
htmx is supposed to let you write semantic HTML, but it's obviously not semantic HTML/HTTP to respond HTTP 200 to incorrect user input. But I think OP is doing this because if they had responded HTTP 400 - Bad Request, htmx would have thrown away the response body by default.[0]
I've been building something with HTMX since the last week, I have not done whole lot of complex things with it but I don't think it will pose any problem when time comes.
I get the premise of HTMX and when and why to use it, it's not solution to everything however it is a blessing for backend developers' who wants to work on frontend.
-> A bit of backstory
For my project Daestro[0], which is bit complex (and big) I chose Rust as backend and Svelte (with Sveltekit) as frontend SPA app. This was my first time working on both. After years of working on Django, I wanted to try statically typed language, after some research and trial, I chose Rust. Sveltekit was obvious because it made sense to me compared to other frameworks and it was super easy to pick up.
After working on Sveltekit for a year, I realised I've been spending a lot of time doing these same thing:
1. You create the api on the backend
2. then you create Zod schema on the frontend for form validation
3. the create +page.ts to initialize the form
4. in +page.svelte you create the actual form and validate it there itself with zod before sending it to the server
Hopping over two code bases just for a simple form, and Daestro has a lot of forms. I was just exhausted with this. Then HTMX started to get a lot of traction, I was watching it from a distant but having worked with Django and it's template, I was dismissive of it and thought having separate frontend is best approach.
-> Why I'm leaning towards HTMX now?
- Askama (rust crate) is a template engine which is compile time checked
- Askama supports block fragments[1], which is you can render certain part (block) of template, plus for HTMX usage
- Askama's macro almost don't make me miss Svelte's components
- Rust has amazing type system, now you can just use it, no need to replicate those on Typescript
- same codebase, no more hopping
- only one binary to deploy (currently for Daestro I've 3 separate deployments)
-> My rules for using HTMX
You must self-impose a set of rules on how you want to use HTMX, otherwise things can get out of you hand and instead of solving a problem you'll create bigger ones. These are my rules:
- Keep your site Multi-page Application and sprinkle some HTMX to make it SPA like on per page basis
- make use of hx-target header to only send the block fragments that is required by HTMX (very easy with Askama)
- do not create routes with partial page rendering instead a route must render complete page, and then use block framents to render only what is being asked in hx-target
- Do not compromise on security[2]
I liked the article a lot, thanks to the author for writing it and sharing it.
At that point, personally I think it'd be easier to use Preact with a no-build workflow for those bits of the app that have a lot of contained logic themselves, and don't necessarily require a round-trip to the server.
I've been building a largish webapp with htmx and I've leaned into web components for these more complicated interactions. I've found htmx great for everything that _should_ involve a call to the backend, anything that does need to fetch data or perform some crud operations, then i can return the necessary markup with oob swaps etc. and mostly forget about client side state
But yeah it's great to see people sharing their approaches!
34 comments
[ 2.8 ms ] story [ 62.3 ms ] threadI think at least some of these issues can be avoided with a different UI/UX to avoid passing temporal/unsaved data between screens.
looking forward to the next instalment!
Then solving this by recreating the entire stepper html at each step, with the added complexity that if it contains something you want to keep "it's a nightmare"?
Then having to create a temporary server-side session to store data that somehow the browser can't keep between two clicks?
Etc.. it's write web apps like it's 1999.
Why is every developer trying to make things complicated?
I usually solve the second problem by simply saving the state of the individual input fields, you only need a user session. Depending your use-case, you might need to be transactional, but you can still do this saving everything as "partial" and close the "transaction" (whatever it might mean in the given context) at the last step. Much-much simpler than sending form data over and over.
In datastar the "Out Of Band" updates is a first class notion.
[1] https://data-star.dev
This blogpost affirms it
Why not use cookies?
However, it's difficult to get things right. I spent way too much time on some basic features that I could have shipped quicker if I used React.
The issue with React though, is that you end up with a ton of dependencies, which makes your app harder to maintain in the long-term. For example, I have to use a third-party library called react-hook-form to build forms, when I can do the same thing using plain HTML and a few AlpineJS directives if I need dynamic fields.
I'm not sure if I'll ever build an app using HTMX again but we need more people to write about it so that we can nail down patterns for quickly building server rendered reactive UIs.
I want to make the intent of this blog post extremely clear (which tragically got lost when I got deep into the writing).
I love HTMX, and I've built entire sites around it. But all over the internet, I've seen HTMX praised as this pristine perfect one-stop-solution that makes all problems simple & easy (or at least... easier than any framework could ever do).
This is a sentiment I have not found to be true in my work, and even one where the author of HTMX spoke out against (although I can't find the link :(
It's not a bad solution (it's actually a very good solution), but in real production sites, you will find yourself scratching your head sometimes. For most applications, I believe it will make ALMOST everything simpler (and lighter) than traditional SPA frameworks.
But for some "parts" of it, it is a little tricker, do read "When Should You Use Hypermedia?" [1];
In the next blog post (where we'll be implementing the "REAL" killer features), I hope to demonstrate that "yes, HTMX can do this, but it's not all sunshine & rainbows."
---
On a completely separate note, one may ask, then, "why use HTMX?" Personally, for me, it's not even about the features of HTMX. It's actually all about rendering HTML in the backend with something like Templ [2] (or any type-safe html templating language).
With Templ (or any type-safe templating language), I get to render UI from the server in a type-safe language (Golang) accessing properties that I KNOW exist in my data model. As in, the application literally won't compile & run if I reference a property in the UI that doesn't exist or is of the incorrect type.
You don't get that with a middle-man API communication layer maintained between frontend and backend.
All I need now is reactivity, and htmx was the answer. Hope you understand!
[1] https://htmx.org/essays/when-to-use-hypermedia/#if-your-ui-h...
[2] https://templ.guide/
I've been programming on personal projects with html-form (my own lib that is a radically paired down version of HTMX with a focus on using native form controls, which makes it so you don't need hydration like you do in HTMX).
So, some thoughts on your problem.
One note. Many people mention OOB. But there is also hx-select, which can be quite handy.
I think there are multiple different approaches that are possible, that I can think of, depending on your needs.
Use eventing. On the server create an event that will be called on the front end by HTMX. When this is called have some vanilla JS that updates the class on your location in the form.
Send all the forms down and have them only visible based on CSS or using an input element is checked or not or using JS. That would be pretty straight forward.
If the form passes then replace all the body contents or main element. If it doesn't just update the form and add any user information. Or send an event back which causes a popup to tell the user what their issue is. Or send an even that causes some text to show.
Also, you can also use CSS and native HTML form validation that will show text underneath an input (or above the input) that tells the user what the the input requires. Modern CSS has a pseudo selector which only turns on when the input is invalid[1].
This is simple enough that a straight up MPA could be used with page animations between pages using view transitions[2].
Similar to one of the solutions above, but using data-action style of programming.[3]
But, I usually try to not over complicate things.
[1]: https://developer.mozilla.org/en-US/docs/Web/CSS/:user-inval...
[2]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_view_tr...
[3]: https://www.lorenstew.art/blog/frontend-event-system
From reading this, I’ve decided I will never attempt to use it again. All I could think was, just use Go’s HTML templating. What is HTMX adding, really?
I remember, working with a co-worker, we planned out the process (a step-by-step on the application like this post) and it made sense to me - but this journey was much harder for my co-worker.
Why is this? Simply because he is familiar with MVC pattern of sending json data back and forth, and getting the frontend to update and render it, etc. The idea of html flying with htmx as the behaviour (inside html tags) was just too much.
For me, I always preferred the old school way of just letting the server-side generate the html. All htmx does is adds extra functionality.
I tried hard to explain that we are sending html back, and to break things down one at a time but each new task was left scratching his head.
In the end, our website had somewhere around 20-50 lines of javascript! Much smaller footprint than over 400 lines in our previous project (that being generous). Sure, our server side code was larger, but it was all organised into View/PartalView files. To me, it made really good sense.
In the end, I dont think I won htmx over with my co-worker. As for another co-worker, who had a chance to build a new project with htmx, decided on some client javascript tool instead. I dont think I got a legit answer why he did that.
With all this above, I learned that some (perhaps most... perhaps all) struggle to adapt to htmx based on their many years building websites a particular way, with popular tools and javascript libraries. Overall htmx does not really change anything - you are still building a website. If anything htmx just add an additional layer to have website really work.
Yoda's words now have new meaning :- "No! No different. Only different in your mind. You must unlearn what you have learned."
For some its just not happening. I guess Luke Skywalker really shows his willpower, being able to adapt to htmx easier than others. :-)
I think a lot of the arguments over HTMX come down to this difference. The people that love it see how much better it is for their use case than something like React, while the critics are finding it can't replace bigger frameworks for more demanding projects.
(Here's an example interface made with HTMX. IMO React would have been overkill for this compared to how simple it was with HTMX. https://www.bulletyn.co )
I like htmx, but this is probably the weakest part of it.
htmx is supposed to let you write semantic HTML, but it's obviously not semantic HTML/HTTP to respond HTTP 200 to incorrect user input. But I think OP is doing this because if they had responded HTTP 400 - Bad Request, htmx would have thrown away the response body by default.[0]
[0] https://htmx.org/docs/#modifying_swapping_behavior_with_even...
I get the premise of HTMX and when and why to use it, it's not solution to everything however it is a blessing for backend developers' who wants to work on frontend.
-> A bit of backstory
For my project Daestro[0], which is bit complex (and big) I chose Rust as backend and Svelte (with Sveltekit) as frontend SPA app. This was my first time working on both. After years of working on Django, I wanted to try statically typed language, after some research and trial, I chose Rust. Sveltekit was obvious because it made sense to me compared to other frameworks and it was super easy to pick up.
After working on Sveltekit for a year, I realised I've been spending a lot of time doing these same thing: 1. You create the api on the backend 2. then you create Zod schema on the frontend for form validation 3. the create +page.ts to initialize the form 4. in +page.svelte you create the actual form and validate it there itself with zod before sending it to the server
Hopping over two code bases just for a simple form, and Daestro has a lot of forms. I was just exhausted with this. Then HTMX started to get a lot of traction, I was watching it from a distant but having worked with Django and it's template, I was dismissive of it and thought having separate frontend is best approach.
-> Why I'm leaning towards HTMX now?
- Askama (rust crate) is a template engine which is compile time checked - Askama supports block fragments[1], which is you can render certain part (block) of template, plus for HTMX usage - Askama's macro almost don't make me miss Svelte's components - Rust has amazing type system, now you can just use it, no need to replicate those on Typescript - same codebase, no more hopping - only one binary to deploy (currently for Daestro I've 3 separate deployments)
-> My rules for using HTMX
You must self-impose a set of rules on how you want to use HTMX, otherwise things can get out of you hand and instead of solving a problem you'll create bigger ones. These are my rules:
- Keep your site Multi-page Application and sprinkle some HTMX to make it SPA like on per page basis - make use of hx-target header to only send the block fragments that is required by HTMX (very easy with Askama) - do not create routes with partial page rendering instead a route must render complete page, and then use block framents to render only what is being asked in hx-target - Do not compromise on security[2]
[0]: https://daestro.com [1]: https://askama.readthedocs.io/en/stable/template_syntax.html... [2]: https://htmx.org/docs/#security
At that point, personally I think it'd be easier to use Preact with a no-build workflow for those bits of the app that have a lot of contained logic themselves, and don't necessarily require a round-trip to the server.
I wouldn't use HTMX for that specific use case.
But yeah it's great to see people sharing their approaches!
can’t wait to steal this uploader for my https://harcstack.org project
[HTMX, Air, Red and Cro]