HTMX: Rethinking the Frontend - Forget React
Daniel Hayes
Full-Stack Engineer ยท Leapcell

Front-End Development Completely Held Captive by JavaScript
Once upon a time, I could effortlessly use the Django template to build web UIs. For the repetitive parts of different sizes on the page, I would utilize templates or fragments to abstract or encapsulate them. Of course, if necessary, I didn't shy away from writing JavaScript to add interactivity to the page.
However, the UI built in this way has obvious flaws. Every time a user interacts with the page, the backend needs to resend the entire HTML page. This not only wastes bandwidth but also makes the interaction clumsy and unsmooth. To enhance the interaction capabilities, some AJAX libraries emerged. As time went by, JavaScript took on more and more tasks, and even the operation of rendering HTML templates on the server side gradually shifted to the client side. Eventually, reactive component-based UIs represented by React ushered in a spring of development.
React brought many revolutionary concepts to web development, such as the virtual DOM, one-way data flow, JSX, and component-based thinking. It transformed the front end from an HTML-centric client to a JavaScript-centric client completely. At the same time, the backend also stepped back from the front-end rendering stage, giving the dominance of generating HTML to the front end while focusing on providing data APIs. But when JavaScript started to take over front-end development completely and HTML became a secondary role, things began to get a bit out of control. Even the footer content that is completely composed of static HTML has to be implemented through JavaScript (JSX). The originally content-rich HTML page has become extremely simple, and there is often only a root element for mounting left in the body.
While React became popular in the front-end world, its flaws gradually emerged. To solve these problems, new ideas, frameworks, and ecological tools keep emerging. Enthusiastic front-end developers, like paving the way through mountains and building bridges over rivers, keep finding solutions to the emerging problems. When there was no suitable state management tool, Redux was created; when the state management was considered too complicated, Hooks were introduced; when the JavaScript client was not friendly to SEO, technologies like SSR were adopted. With the continuous stacking of various solutions, the originally simple solution has become more and more complex, and finally formed a huge and messy "Frankenstein". Nowadays, I have a sense of fear towards front-end development. The originally simple tasks are now full of various cumbersome details. When writing front-end code, I am often overwhelmed by the excessive complexity.
What's worse, due to JavaScript's complete takeover of front-end development, the scale of front-end projects keeps increasing, which also makes TypeScript the best practice. I'm not belittling TypeScript. In fact, TypeScript is a well-designed language that solves many problems that JavaScript has in large front-end projects very well. But do we really need "large" front-end projects everywhere? What is the root cause that makes front-end projects so cumbersome and bloated? Initially, weren't the main purposes of these frameworks to make the front end more reactive, easier to reuse, and more expressive? However, now, React and many front-end frameworks that have emerged one after another, such as Vue, SolidJS, Svelte, etc., their complexity forces front-end developers, especially "pseudo front-end developers" like me, to turn small projects into large ones and simple projects into complex ones. Therefore, in order to deal with complex projects, choosing TypeScript seems to be inevitable.
HTMX: Returning to the Original Aspiration of HTML
Although I'm not sure where the name HTMX comes from, according to its vision, I guess it might mean HTML eXtension. HTMX believes that we should focus on enhancing and developing HTML. Many of HTML's flaws can be made up for through better semanticization, such as making reasonable use of tag attributes, instead of directly letting JavaScript replace HTML. This is the direct introduction of it on htmx.org: "htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext".
The core vision and features of HTMX include:
- Progressive Enhancement: HTMX is designed based on the principle of progressive enhancement, which means that it first ensures that the page can work properly without JavaScript, and then gradually adds more functions.
- Low Invasiveness: HTMX strives to have the least invasiveness to existing code. You don't need to rewrite the entire application. You can start using HTMX just by adding some attributes where needed.
- Pure HTML: With HTMX, you can implement many complex front-end functions without writing JavaScript. This makes the code easier to understand and maintain, especially suitable for developers who are more familiar with HTML and server-side programming.
- Compatibility with Existing Technologies: HTMX can seamlessly collaborate with your existing frameworks and libraries. Whether you are using Flask, Django, Rails, or other backend frameworks, HTMX can be easily embedded in them.
- Small and Fast: Compared with many modern front-end frameworks, HTMX is very lightweight, which means it loads faster and can enhance the user experience.
From these features, we can see that the goal of HTMX is to simplify front-end development, enabling developers to quickly and efficiently create highly interactive and responsive web pages while avoiding using a large amount of JavaScript or complex front-end frameworks.
When you don't need complex front-end frameworks and a large amount of JavaScript development, you will find that many of the problems currently faced by the front end are easily solved. For example, there is no need to completely JavaScript-ify the entire page, no need to worry about the SEO problems caused by the JavaScript-ification of the page, no need to manage complex states, and no need to introduce TypeScript to solve engineering problems.
Comparison of Sample Codes
Let's first take a look at how to implement the typical front-end function: autocomplete, under HTMX.
<input type="text" hx-trigger="keyup delay:500ms, custom-event" hx-get="/search" hx-target="#search-results" hx-swap="innerHTML"> <div id="search-results"></div>
Don't be too surprised. This small piece of code is all the code for the HTMX version of autocomplete. We can see that HTMX adds several important attributes to the ordinary HTML tag:
- hx-trigger: It is used to specify when and how to trigger an htmx action, such as an AJAX call. Through this attribute, developers can control how to initiate an interaction with the server when certain events occur (for example, click, input, or focus, etc.). In this example, two trigger events are set: a) Trigger when the keyup event occurs on the input and after a 500ms delay; b) Trigger when an event named custom-event is received.
- hx-get: When the htmx action is triggered, the call to be executed. hx-get represents a GET request. Similarly, hx-post, hx-put, hx-delete, hx-patch, etc. can also be used for server calls. Through attributes like hx-get, HTMX decentralizes the right to interact with the server to each tag, changing the traditional limitation that only <a/> and <form/> can interact with the server.
- hx-target: When the server's response is returned, it specifies the location where the response will be filled. hx-target can be any CSS expression. Here, it points to the node with the id of search-results. By default, it is the current node. If attributes like hx-get provide the ability to interact with the server in the page, then hx-target provides the ability to dynamically update in the page.
- hx-swap: When the server's response is returned, it specifies how the content should be swapped or replaced. By default, it is innerHTML, that is, the HTML inside #search-results will be replaced by the data returned by the server. hx-swap has other behaviors, such as outerHTML, beforeend, afterend, and it is even possible to add animation effects for how to do the swap. You can check the documentation for details.
These attributes are very useful for beginners. Mastering them can handle most of the in-page interactions. HTMX also provides many other hx-* attributes, which will not be introduced one by one here. Using these attributes, we can easily control the behavior of the search box and achieve the effects that originally required a lot of JavaScript to achieve.
Let's look at a more complex example:
Suppose the application displays several notebooks, each notebook has several notes, and each note has detailed information. We display it in a three-column layout. When the user clicks on book1 in the leftmost column, the notes under book1 will be displayed in a paginated form in the second column, and then the details of the first note in the second column will be displayed in the third column.
Here is the code implemented using HTMX:
- Code for the Left Column:
<ul> {% for book in books %} <li> <a hx-get="/books/{{book.id}}" hx-target="#note-list">{{book.name}}</a> </li> {% endfor %} </ul>
- Code for the Middle Column:
<div id="note-list"> {% for note in current_book.notes %} <div onclick="htmx.trigger('#note-detail', 'loadNote', {id: '{{note.id}}'})"> <h2>{{note.title}}</h2> <p>{{note.summary}}</p> </div> {% endfor %} </div>
- Code for the Right Column:
<div id="note-detail" _="on loadNote(data) from body htmx.ajax('GET', `/notes/${data.id}`, '#note-detail')" > <h3>{{title}}</h3> <p>{{detail}}</p> </div>
In this way, through a few simple templates and supplemented with HTMX attributes, not only is the first page rendering achieved, but the page can also be updated according to the user's clicks. For example, when the user clicks on book2, a GET request will be triggered to access /books/2, and the following response will be returned (that is, the content generated by the template of the middle column):
200 OK
HX-Trigger: {"loadNote": {"id": "book2id1"}}
Content-Type: text/html
<div onclick="htmx.trigger('#note-detail', 'loadNote', {id: 'book2id1'})">
<h2>Hello 1</h2>
<p>World 1</p>
</div>
<div onclick="htmx.trigger('#note-detail', 'loadNote', {id: 'book2id2'})">
<h2>Hello 2</h2>
<p>World 2</p>
</div>
...
This result will be rendered by HTMX into #note-list, thus achieving the update of the middle column. At the same time, since the loadNote event is included in the HX-Trigger header of the returned data, this event will be captured by #node-detail and send a GET request to /notes/book2id1, and then its response will be rendered into the right column.
For the above functions, if implemented using React, the amount of code will increase significantly, and more state management and component interaction logic need to be handled. Here is a simple React implementation example (simplified version, only showing the core logic):
import React, { useState, useEffect } from'react'; const BookList = ({ books }) => { const [currentBook, setCurrentBook] = useState(null); const [currentNote, setCurrentNote] = useState(null); useEffect(() => { if (books.length > 0) { setCurrentBook(books[0]); if (books[0].notes.length > 0) { setCurrentNote(books[0].notes[0]); } } }, [books]); const handleBookClick = (book) => { setCurrentBook(book); if (book.notes.length > 0) { setCurrentNote(book.notes[0]); } }; const handleNoteClick = (note) => { setCurrentNote(note); }; return ( <div className="flex"> <div className="w-1/3"> <ul> {books.map((book) => ( <li key={book.id} onClick={() => handleBookClick(book)}> {book.name} </li> ))} </ul> </div> <div className="w-1/3"> {currentBook && ( <div> {currentBook.notes.map((note) => ( <div key={note.id} onClick={() => handleNoteClick(note)}> <h2>{note.title}</h2> <p>{note.summary}</p> </div> ))} </div> )} </div> <div className="w-1/3"> {currentNote && ( <div> <h3>{currentNote.title}</h3> <p>{currentNote.detail}</p> </div> )} </div> </div> ); }; export default BookList;
It can be seen that to implement the same function using React, multiple state variables (such as currentBook and currentNote) need to be defined to manage the page state, and more event handling functions (such as handleBookClick and handleNoteClick) need to be written in the component to handle user interactions. In contrast, the implementation method of HTMX is more concise, the logic is clearer, and it is closer to the traditional server rendering idea.
Conclusion
HTMX has reopened the door to front-end development for non-front-end engineers. If you are not developing applications with extremely high interactivity like spreadsheets or Google Maps, basically, you can make good use of HTMX to replace the existing front-end development frameworks and return to the lightweight front-end development mode centered around HTML. With HTMX, you don't need to struggle with whether to implement the client as a SPA or an MPA. You can choose the most suitable way for routing, display data in the most natural way, and let users interact with the data (whether it is creating, reading, updating, deleting, or other operations).
Currently, the HTMX ecosystem is still in its infancy. I am really looking forward to the mainstream backend frameworks providing in-depth support or even integration for it. I believe that as the value of HTMX is continuously discovered, non-front-end developers will be able to regain their confidence and easily develop complete products that include web front ends.
Leapcell: The Best of Serverless Web Hosting
Finally, I would like to recommend a platform that is most suitable for deploying web services: Leapcell
๐ Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
๐ Deploy Unlimited Projects for Free
Only pay for what you useโno requests, no charges.
โก Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.
๐ Explore Our Documentation
๐น Follow us on Twitter: @LeapcellHQ