The web has been waiting for HTMX

bl42

Benjamin Lacy

Posted on July 17, 2023

The web has been waiting for HTMX

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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...";
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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" 
}
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode

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

  1. Wait for app.js to download.
  2. Parse and run inside the V8 engine.
  3. Rush to the First Paint, rendering an app shell and "loading" state.
  4. We segmented our application by code-splitting, so we need to repeat step first two steps with more code.
  5. Make a JSON request to fetch the tweet.
  6. Convert the data to HTML (or some JavaScript equivalent).
  7. Re-render the html with the new data.

HTML as Data

  1. Request the HTML.
  2. 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> 
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>   
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
bl42
Benjamin Lacy

Posted on July 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

The web has been waiting for HTMX
javascript The web has been waiting for HTMX

July 17, 2023