Rethinking the frontend with HTMX
I’ve done a lot of work with FastAPI, and that led me to FastHTML. Now this library is far from ready to real use, but one of its components was HTMX, so I branched off to take a look at that. What I found surprised me and made me do some serious rethinking on how front ends are developed.
What is HTMX?
HTMX is a tiny javascript library which basically allows you to trigger Ajax calls and deal with the responses using attributes set in html tags. The big thing is that it expects the responses to be html snippets, which it can then place in the current page.
So a simple usecase is that an ajax call is triggered and the response is html that is used to replace the element that triggered the call. I looked that this and tried it out. My first thought was WHY!! A second look, then a third and I began to see why HTMX could possibly change Front end in a huge way, especially if your using a good backend with a powerful templating engine
The WHY of HTMX
HTMX requires you to rethink how you do front end coding. For a long time we have worked on the idea that the front end does Ajax calls that return json, which we then render into templates, or make changes to elements in the dom. HTMX basically says that all rendering is done by the backend instead. No more sending JSON, and altering elements with Javascript, instead we get HTML back and just replace/add to elements on the front end. We basically shift away from Javascript on the front end doing most of the work, to using the backend and template engines on the backend to deliver ready to use html snippets.
So all HTMX needs to do is send the Ajax call based on the right event and then decide what is going to happen to the html when its returned.
The Simplicity of HTMX
To keep things simple, I’m going to break down HTMX into three parts.
- Details on the event that will be the trigger
- Details on the call and what should be sent
- Details on what to do with the html returned by the call.
The documentation can be a little confusing, so I’m going to take a different approach from them by dealing with each of the parts in turn
1. Triggering
First of lets take a look at a really simple example
<!DOCTYPE html>
<html>
<head>
<title>Simple HTMX Example</title>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
</head>
<body>
<button hx-get="/data" hx-trigger="click" hx-swap="outerHTML">
Click Me
</button>
</body>
</html>The only attribute I’m going to talk about here is the hx-trigger. As you can see its set to trigger on a click of the button element. That said, the attribute did not need to be stated. The default event is “click” except for input elements which are set to“change” and forms that are set to “submit.
For the other attributes, hx-get says to do a get request to the url, and hx-swap=”outerHTML” states to replace the whole button with the html snippet returned. More on those later, lets talk about triggers.
The Trigger event can be any event supported by the standard WebAPI, including keyboard and mouse events. Thus you can trigger when the mouse pointer leaves an element, or when a key is pressed while adding to an input element.
We can however use a series of modifiers to alter when a trigger occurs.
<!DOCTYPE html>
<html>
<head>
<title>Simple HTMX Example</title>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
</head>
<body>
<div hx-get="/data" hx-trigger="every 1s" hx-swap="outerHTML">
updated even 1 second
</div>
</body>
</html>the item above will trigger every 1 second, thus allow for polling of the backend.
The next example is where the hx-trigger is pointed at a different element. In this case the Ajax call is made when the button is clicked.
<!DOCTYPE html>
<html>
<head>
<title>HTMX Example with Div Trigger</title>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
</head>
<body>
<div id="my-div" hx-get="/data" hx-trigger="click from:#button1" hx-swap="outerHTML">
Initial content of the div.
</div>
<button id="button1">Click me to update div</button>
</body>
</html>There are also other “Modifiers” to the triggers.
- changed :> only trigger if the value of the element has changed
- delay:<time> :> Delay the action for the set amount of time (1s, 2s, etc)
- throttle:<time> :> Will delay the request being set, but also cancel any further events causing a trigger for the set time period. Good to stopping users clicking a button to much.
1.1 Triggers: Race Conditions
So imagine that we have a form and inside the form, we have an input element that is using htmx to validate the users input. How can we stop the user from submitting the form while the validation is occurring.
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
hx-sync="closest form:drop">
<button type="submit">Submit</button>
</form>The hx-sync here is saying that if an event is triggered on the closest form, then the new event should be dropped or not run. Thus the validation of the input element has priority and the submission request is rejected.
We could of course go the other way
<form hx-post="/store">
<input id="title" name="title" type="text"
hx-post="/validate"
hx-trigger="change"
hx-sync="closest form:abort">
<button type="submit">Submit</button>
</form>In this one, the validation request is cancelled.
Another use of hx-sync is what happens when we get multiple events. We have already seen that the “throttle” modifier can be used to cancel other events for a give time period, but with hx-sync, we can cancel the current request and replace it with the new one
<input type="search"
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results"
hx-sync="this:replace">1.2 Triggers: Showing something is happening
A problem we could have here is that while a ajax call has been triggered, the user is not informed of this. They can’t see their click as done anything. HTMX has an attribute that deals with this.
<button hx-get="/click">
Click Me!
<img class="htmx-indicator" src="/spinner.gif">
</button>In this example, the “img” tag is hidden. Its opacity is set to 0. When the button is clicked, HTMX will search out all the child elements with a class of “htmx-indicator” and set opacity to 1, thus they can now be seen. You can use this so the user can see that something is happening.
From all of this, you can see HTMX is very flexible when dealing with what events are triggered and how those events should be handled. Next we will discuss the actual Ajax request.
2. The Request
Of the three sections, the Request is the easiest part to deal with. First off we have attributes for the standard request types hx-post, hx-get, hx-put, hx-patch, and hx-delete. These all point to a given url. The more complicated part is dealing with what data is sent along with the request.
For an input element, the value of the input is automatically sent. Similar when we submit a form, all the input values are sent. Yet what happens if we want to send additional values. I use Django alot and so the CSRF token has to be sent with all post requests. I could make all the posts into forms, but that’s messy. HTMX has a couple of additional attributes to help with this.
“hx-include” allows you to pull in values from other elements on your page.
HTML
<!DOCTYPE html>
<html>
<head>
<title>HTMX Multiple Element Inclusion</title>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
</head>
<body>
<input type="text" id="password1" name="password1">
<input type="text" id="password2" name="password2"
hx-post="/validate/password" hx-include="[name='password1']">
</body>
</html>A second way is to use the hx-vals attribute
HTML
<!DOCTYPE html>
<html>
<head>
<title>HTMX Multiple Element Inclusion</title>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
</head>
<body>
<input type="text" id="password1" name="password1">
<input type="text" id="password2" name="password2"
hx-post="/validate/password" hx-vals="js:{csrftoken: 'dfdfdsdsf'}">
</body>
</html>hx-vals supports json and js scripted json. so {“csrftoken”:”dfdfd”} or js:{csrftoken: get_csrf()}
That’s all we really need to do for the requests side. Given that we are rendering the html via a template engine, we can add in extra parameters and of course style urls to include parameters themselves.
3. The Response
This is the Biggie. Its where we really start to see why you should consider HTMX for the front end.
As a default, HTMX takes the response returned and replaces the element that sent the request, thus hx-target=”outerHTML”. No need to state this. Now we get into what happens if we want the response to be placed elsewhere.
“hx-target” can be used to point to another element by its css path for example
<input type="search"
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results">
<div id="search-results"></div>
The big difference is that the response replaces the innerHTML, so in this case the response replaces anything inside the div.
To make this more flexible, we have “hx-swap”. The example below shows the hx-swap as it would be for the default.
<input type="search"
hx-get="/search"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#search-results" hx-swap="innerHTML">
<div id="search-results"></div>“hx-swap” has other options though
- outerHTML : Replaces the whole target element
- innerHTML : Replaces the content of the target element
- afterbegin : Adds the content as the first child of the element
- beforebegin: Add the content directly above the target
- beforeend: Add the content after the last child of the target
- afterend: Add the content directly below the target
- delete: don’t do anything with the response and delete the target element.
I will also mention “hx-select” here. It allows you to state part of the response you want to use in your swap. This is where we start getting into the idea that your request could return multiple elements, thus the same url could be used to update multiple elements. “hx-select” allows you to use css identifiers to select the element in the response you want to use for your swap.
So your now thinking that your going to end up calling the same url to create a bunch of elements and use them to swap different sections of the page. Why can’t we update all the sections in one call. Well we can.
“hx-swap-oob” allows us to do this. OOB stands for out-of-bounds. Basically it says a given element is to be swapped, but its not stated in the htmx attributes of the requesting element. What we do instead is place the “hx-swap-oob” on the elements that are returned from the request. So an example response will be
<div id="message" hx-swap-oob="true">Swap me directly!</div>When we get this response, htmx will search around and find the element “#message” and replace it. We can also have multiple elements in the response.
Sounds good, but we can take this a little further
<div hx-swap-oob="beforeend:#messages">add me!</div>With this our response is added as the last child in the messages element.
This is not the end
I’m stopping this article at this point because there is quite a lot to get to grips with here. HTMX is a tiny framework, but with just a few attributes, it can replace most, if not all, of your standard front end script requirements. It also does this using your backend to render your templates.
If your coding a django website, then this just makes front end coding as simple as it can be. just get used to sending snippets of html instead of whole pages.
However this is NOT all HTMX can do. It can be used to trigger javascript functions using hx-on, deal with websockets and SSE, user prompts and confirmations, error handling and a lot more stuff. The thing is, it can do nearly all of this without any JS script.
Lets take a standard example of displaying a modal. Using JS, we call the backend get json back, update the modal html, and then set the css so the modal shows. With HTMX, we call the backend and it just replaces the whole modal element, which is then displayed. Pretty simple especially when you consider the templating engine will make the rendering as easy as anything.
