Django, HTMX and Alpine.js: A Match Made In Heaven
Created 2021-06-06. Modified 2024-04-06.
If you want to see HTMX and Alpine.JS in action together, click here to see a basic project I put together that shows them in action.
Table Of Contents
- Introduction
- What is HTMX?
- What is Alpine.JS?
- Why Use HTMX and Alpine.JS Together?
- How Well Do HTMX and Alpine.JS Work Together?
- Notes on Using HTMX with Django
- Conclusion
Introduction
I've been trying to find a way to take the features of old-school server-driven frameworks such as Django, and find a way to provide a modern web experience such as the ones provided by the frontend Javascript frameworks such as React, Vue, Angular, and Svelte (among others). Namely, I want to be able to update elements of the page while still preserving the state of the web application at any given time. I also want to benefit from the ease with which these frameworks allow you to add functionality to a web app without all the boilerplate.
Django (and other server-side frameworks) can be made to play nice with these frontend frameworks, but this involves the use of a REST framework that restricts the usefulness of Django, particularly its ability to render HTML templates, and also its session management capabilities, which make it easy to manage user authentication and sessions.
Although Django works very well for me, it still feels as though it was designed for a different era. By default, it produces plain HTML templates which contain links which, when clicked, will blank out the screen and fill it with a newly-fetched template. While there is no rule that it must be used that way, this is how I have come to understand the flow of Django. There is JSONResponse
, but this requires the data to be processed, DOM elements manipulated, etc.
What if there was a way to preserve the bulk of a given page's content, but easily swap certain parts of the page with new data?
This is where HTMX comes in.
What is HTMX?
HTMX is described by its creator as "high power tools for HTML".
It functions a lot like an ajax or fetch()
call, but with a couple significant differences:
- HTMX allows for GET/POST/PUT/DELETE/etc. calls from any element, not just
<form>
and<a>
. - HTMX allows you to specify a DOM target to replace with the newly-fetched data.
- Instead of fetching JSON data, HTMX (by default) expects to receive HTML content.
Using these principles, HTMX helps to put some of the power back into your server-side framework (e.g. Django), since you are no longer required to process the incoming data. Just create a view that acts as a pseudo-endpoint, render the data into a template, and insert that into your target DOM element.
Here is an example of how HTMX is used:
<button hx-post="/clicked"
hx-swap="outerHTML"
hx-target="#your-target-element">
Click Me
</button>
<div id="#your-target-element">
This content will be replaced.
</div>
hx-post
allows you to specify the request method used (GET/POST/PUT/DELETE/etc.)hx-target
specifies the CSS selector of the element whose content will be replaced when the server returns a response.hx-swap
describes how the incoming data will be placed in relation to the target element. The new data can be appended/prepended to the old data, it can replace the old data entirely, etc.
As you can see, the addition of a few HTML attributes allows your HTML to become a lot more capable of producing an app-like experience.
Although my demo project only uses HTMX before basic fetching and swapping, HTMX is a rather versatile library that can perform quite a few tasks. You can see some example's of HTMX's power on its examples page.
What is Alpine.JS?
In short, Alpine.JS is a lighter version of Vue.JS. It is a small library that allows you to easily add reactivity to your web apps (ie. change in state is immediately reflected in the application), as well as some other quality-of-life features. Like HTMX, it is a Javascript library that is used by adding inline HTML attributes.
Here is an example of a basic Alpine component:
<div x-data="myComponent()">
<div>
The value is <span x-text="myValue"></span>.
</div>
</div>
<script>
function myComponent() {
return {
myValue: 5
}
}
// The component will be rendered as "The value is 5."
</script>
Alpine.JS can be used for transitions, conditional rendering, and event handling, iterate over arrays... It really is Vue's little sibling. Even the syntax is nearly identical: Compare the HTML syntax to create an event listener: v-on:click
(Vue) vs x-on:click
(Alpine). Alpine even borrows the same shorthand syntax (they both use @click
as shorthand to create a click event listener).
So why use Alpine instead of just using Vue? Vue uses a virtual DOM (aka VDOM) to more efficiently manage the application state before updating the actual DOM. Anything that directly manipulates the DOM (e.g. jQuery) may cause problems with Vue because it uses a VDOM, which gets tripped up when something is manipulating the DOM directly without the VDOM knowing about it. Unlike Vue, Alpine does not use a VDOM, but instead manipulates the DOM directly. This should mean that any HTML templates fetched by HTMX should not interfere with Alpine's functionality as they would with a framework that uses a VDOM.
Note that Vue will generally beat Alpine in benchmarks.
Like the other frameworks, Alpine has a browser extension that allows you to detect, inspect, edit, and debug Alpine.js data and components. It is called Alpine.js devtools, and it is available for both Chrome and Firefox
Alpine also has a third-party state-management layer called Spruce that provides a way for components to communicate with each other, and acts as a single source of truth for your app's data. It is like React Redux or Vuex.
Update: Alpine v3 now has a built-in state management system.
Why Use HTMX and Alpine.JS Together?
For my uses, HTMX does the work of swapping page elements, while Alpine is used for things like toggling navbars and modals, setting key event listeners to close the modals, and adding transition effects to both.
I have found that HTMX does the bulk of the work when adding an SPA-like feel to a web app, while Alpine helps to improve the flow of the UI and reduce the amount of Javascript I need to write to do common tasks.
Alpine also allows you to set event listeners on the window object, which is handy. For example, you can use the Escape key to close a modal no matter what element is highlighted (the modal is also conditionally rendered using Alpine's x-if
attribute, so the listener is only enabled when the modal is active).
How Well Do HTMX and Alpine.JS Work Together?
Overall, I have found the two to work very well together. I have been able to easily work with both HTMX and Alpine.
I did run into a few problems with the two libraries as I was creating my demo project, but I was unable to duplicate them when I was doing this writeup. (If you have run into any issues mixing the two, please share your experiences in the comments section.)
For client-side form validation, I found that HTMX fires a few events related to form validation, but I found it easier just to handle the submission using Javascript + Alpine, and then use HTMX's Javascript API to call it after doing my validation. For example:
<form x-data="myForm()"
@submit.prevent="submitForm">
<input type="text" id="my-text" name="mytext">
<input type="submit">
</form>
<div id="form-result"></div>
<script>
function myForm() {
return {
submitForm() {
// get the value of #my-text
let myText = document.querySelector(#my-text).value;
// if myText is empty, do not continue
if (!myText) {
console.log('Something went wrong.');
return false;
} else {
// submit the form using HTMX
htmx.ajax(
'POST',
'https://your.server/form-url/', {
target: '#form-result',
values: { text: myText }
});
}
}
}
}
</script>
Notes on Using HTMX with Django
HTMX and Django make a great pair. You can render a response using a template with Django's render
function, or return an HttpResponse
with a string that bears a striking resemblance to JSX:
from django.http import HttpResponse
def my_view(request, your_name):
return HttpResponse(f"""
<div>
Hello, {your_name}!
</div>
""")
You can return scripts:
from django.http import HttpResponse
def my_view(request, your_name):
return HttpResponse(f"""
<script>
// this will execute immediately
console.log("Hello, {your_name}!");
</script>
""")
You can also add status codes to your HTTP responses:
def my_view(request, your_name):
if not your_name.isalpha():
response = HttpResponse("That's not a real name!")
response.status_code = 400 # bad request
return response
return HttpResponse(f"""
<div>
Hello, {your_name}!
</div>
""") # HttpResponse returns status code 200 by default
Note that HTMX doesn't change the content if a 302 response is returned, so you can return 302 if you don't want the content to change:
def my_view(request, your_name):
if not your_name.isalpha():
response = HttpResponse()
response.status_code = 302
return response
return HttpResponse(f"""
<div>
Hello, {your_name}!
</div>
""")
Adding a CSRF Token Header to Your HTMX Requests
Because Django forms (and any other view that accepts a non-GET method) require a CSRF token, the following script must be included to the bottom of your body so that HTMX can automatically add the incoming CSRF token to its header so that Django will accept any non-GET requests from HTMX (credit to Matt Layman):
<script>
document.body.addEventListener(
'htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '';
})
</script>
Conclusion
HTMX and Alpine.JS work very well together and can let you achieve an immersive, fluid web experience. Thanks to HTMX, you can easily update fragments of a page instead of having to completely reload the page. With Alpine.JS, you get reactivity and other quality-of-life additions to your frontend experience. You also get to keep your DOM, and are not subject to the restrictions placed upon you by other frontend frameworks.
Using these two libraries also allows you to keep your server-side framework and all its features, especially templating and session authentication.
I will keep working with HTMX and Alpine.JS and hope that they will continue to work as well for me in the future as they have so far.
Check out my demo project showing HTMX and Alpine.JS in action together.
This blog post is licensed under CC-BY 4.0. Feel free to share and reuse the content as you please. Please provide attribution by linking back to this post.