The web has been waiting for HTMX
Benjamin Lacy
Posted on July 17, 2023
Why now?
Modern web features didn't just stop with Flexbox and View Transitions. New features are constantly being added, and its time to let go of our bloated javascript Libraries/Meta-Frameworks/Frameworks.
With the significant advancements in browsers, our architecture needs to catch up
Wait, What Features??
Let me provide you with a small sample of underrated features that are often overlooked. While this list is not exhaustive, it aims to expand your knowledge of what is possible. The best part is that everything I mention can be used today in all major browsers and even older ones with polyfills.
You don't need to install packages found on a 5-year-old Stack Overflow post.
Intl namespace
Moment.js has been a go-to package for date parsing, timezones, and localization. However, it comes with the cost.
That cost is embedding a database encoded in json holding various translations, timezone preferences, and other locale-specific information.
This database has thing like
... the Tamil translation of "in 3 Days"
... or the fact that Mexico prefers 24-hour Time.
... or the preference of "PM" in the US and "p.m." in Canada
... or every time a city in Indiana decided change its timezone over the last century
You get the idea, we dont need to ship all this data.
The Intl.DateTimeFormat
and Intl.RelativeTimeFormat
APIs provide a simple and flexible solution.
new Intl.RelativeTimeFormat('default', { // en-us (for me)
style: 'long'
}).format(3, 'day');
// output:
// in 3 days
new Intl.DateTimeFormat('en-ca', {
timezone: 'America/Chicago',
dateStyle: 'full',
timeStyle: 'long' }
).format(Date.now());
// output:
// Sunday, July 16, 2023 at 3:09:44 p.m. CDT
These APIs are supported today and offer powerful date and time formatting capabilities.
Form Validation
Instead of installing one of the 2,000+ form validation packages available on npmjs.com, consider utilizing the built-in form validation in browsers. It covers the vast majority of use cases.
<form>
<input type="text" name="username" required pattern="/[A-z0-9]{3,0}/">
<input type="text" name="firstname" required pattern="/[A-z]/">
<input type="text" name="lastname" required pattern="/[A-z]/">
<input type="password" name="password" required pattern="(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$">
<button>Create Account</button>
</form>
This approach, combined with CSS pseudo-selectors, enables powerful styling and validation feedback.
form:has(:invalid):before {
content: "You are missing some stuff";
}
label:has(input[type="password"]:invalid):after {
content: "Password needs to be 8 chataters long...";
}
Side-note: The form submission is disabled until all requirements are met.
HTMLDialogElement
Meet the <dialog>
element, which can be fully customized for handling modals and toasts without requiring any JavaScript.
The <dialog>
element has a unique feature — it renders outside of the DOM tree, even above elements with the highest z-index values.
<div style="overflow: hidden"> <!-- overflow: hidden doesnt effect <dialog> -->
<dialog open>
<p>To confirm type delete</p>
<form method="dialog" method="delete" action='/delete'>
<input type="text" placeholder="delete" pattern="/delete/" required />
<button>OK</button>
</form>
</dialog>
</div>
Its just data!
Consider the following JSON and HTML, What is the difference?
{
"username": "Joe_Black",
"tweet": "Wow, that's incredible! 🎉 Thanks for sharing!",
"timestamp": "2023-07-16T10:30:00Z"
}
<div class="tweet">
<span class="username">Joe_Black:</span>
<span class="content">Wow, that's incredible! 🎉 Thanks for sharing!</span>
<span class="timestamp">Posted on: 2023-07-16T10:30:00Z</span>
</div>
One is encoded in JSON
other is encoded in HTML
. It is the same thing but in 2 different formats.
After the data is loaded, the process of displaying differ significantly.
JSON as Data
- Wait for
app.js
to download. - Parse and run inside the V8 engine.
- Rush to the First Paint, rendering an app shell and "loading" state.
- We segmented our application by code-splitting, so we need to repeat step first two steps with more code.
- Make a JSON request to fetch the tweet.
- Convert the data to HTML (or some JavaScript equivalent).
- Re-render the html with the new data.
HTML as Data
- Request the HTML.
- Append the HTML to the DOM.
There has been two key problems with HTML as Data.
First, it lacks fine-grained control over which parts of the UI change, often resulting in a full refresh. Second, sometimes we need to modify the data on the client side.
Modifying Data on the Client
Lets focus on the timestamp from the previous example.
<span class="timestamp">Posted on: 2023-07-16T10:30:00Z</span>
You may think have been painting a contradiction here "hey there is this really cool javascript API to handle dates... but lets stop using javascript".
There will be times where we need to execute javascript to help render information. The solution is to use customElement
api to create atomic components.
<span class="timestamp">
Posted on:
<intl-formatted-time datetime="2023-07-16T10:30:00Z"></intl-formatted-time>
</span>
These Custom Elements can be used to render the entire application (see youtube's rewrite), but this should be the exception to the rule and not the default for the sites we build.
Svelte has an interesting Custom elements API that can play a roll here.
HTMX
From htmx.org:
HTMX is a small library extending html using custom attribute tags. HTMX is a library that allows you to access modern browser features directly from HTML, rather than using javascript.
All the extending of html is using custom attribute tags prefixed with hx-
.
This gives the building blocks to make single page applications based on HTML communicating with servers responding in HTML (instead of JSON)
<script src="https://unpkg.com/htmx.org@latest"></script>
<nav>
<a hx-get="/@user/tweets"
hx-swap="outerHTML"
hx-target="main">
User's Tweets
</a>
<a hx-get="/profile"
hx-swap="outerHTML"
hx-target="main">
Profile
</a>
</nav>
<main></main>
So lets break this example down.
hx-get
This is a HTTP Verb that is setting the AJAX request method to a url... this can be replaced with other verbs like post, put, & delete.
If you are unfamiliar with "AJAX" (XMLHttpRequest) its like the
fetch()
api. It was really cool back in the day.
hx-swap
The method to modify the DOM with the response of the HTTP request.
hx-target
Which element to target the swap method.
So with this knowledge lets rewrite <a href ...>
in htmx
<a href="/@user/tweets">
User's Tweets
</a>
<!-- is equalivent to -->
<a hx-get="/@user/tweets"
hx-swap="outerHTML"
hx-target=":root">
User's Tweets
</a>
Just with that small knowledge we can do alot and there is so much more to HTMX. This includes loading states, web socket support and even a scripting language called Hyperscript.
It takes only a few minutes to get started and gain a good understanding of its capabilities.
Is HTMX the future?
I have no idea but something like HTMX is a future that I am looking forwards to.
RESTful Components
Components excel at isolating logic and concerns. However, they don't need to reside solely in the browser. I am introducing the concept of "RESTful Components," which are responsible for fetching data and presenting it in HTML format, powered by HTMX.
For example, consider a user's post feed: GET /user/:id/posts
.
<div class="post">...</div>
<div class="post">...</div>
<div class="post">...</div>
<div hx-get="/user/:id/posts?page=2"
hx-trigger="revealed"
hx-swap="outerHTML">
Loading More...
</div>
Little bit of Recursion happing here.
As you scroll to the bottom, hx-trigger="revealed"
triggers the loading of the next page, and the trigger for the 3rd page is subsequently loaded.
Another example: GET /post/:id
<div class="post">
...
<div class="comments"
hx-get="/post/:id/comments"
hx-trigger="intersect once"
hx-swap="innerHTML">
Loading Comments
</div>
</div>
<style>
/** inlined css for demo purposes */
.post {
container-type: inline-size;
container-name: post;
}
.post .comments {
display: none;
}
@container (min-width: 700px) {
.post .comments {
display: block;
}
}
</style>
In this example, we are conditionally loading the comments.
This /post/:id
could be used on dedicated page for the post or as an aside on another page. Size constraints will determine whether comments are loaded.
Conclusion
All these pieces are there, HTMX is the missing glue to build complex HTML driven applications.
There is still much more to explore and I have my reservations.
One is HTMX's HyperScript scares me in a CoffeeScript kinda way ... another is the pattern for SSR type of templating on the backend.
However, the technical advantages and tradeoffs are to strong to ignore.
If you want to be friends reach out to @bl42
Posted on July 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.