Intro to HTMX: Dynamic HTML without JavaScript

HTMX is the HTML extension syntax that replaces JavaScript with simple markup. It could change the course of web development.

PHOTO JUNCTION/Shutterstock

HTMX lets you use an extended HTML syntax instead of JavaScript to achieve interactivity. HTMX gives you HTTP interactions directly in the markup, and it supports many other interactive needs without resorting to JavaScript. It’s an interesting idea that could end up influencing the way web front-ends work. Let’s see how HTMX is used and what makes it so compelling.

What is HTMX?

HTMX has been around for some time, but it has been a bit of a sleeper project. Its recent acceptance into the GitHub Accelerator may change all that. The basic idea is to take common use cases that require boilerplate JavaScript-and-HTML interactions and just use an HTML syntax, without the JavaScript. Many interactions become declarative with HTMX.

This already sounds promising, doesn’t it? Every web developer knows there are many common boilerplate cases. Carson Gross, the creator of HTMX, says he’s hoping to "complete HTML as a hypertext, increasing its expressiveness enough to make it a competitive alternative for more advanced, modern web applications.”

To get a quick taste, see this HTMX demo. Basically, we click on a button to enable editing the fields on the user object. The data is actually PUT into a back-end endpoint. You can see the demo in Figure 1—notice the network interaction in the bottom frame after you click Show

IDG

Figure 1. Form demo: HTMX

Normally, this all would require JavaScript of some kind, no matter what framework you were using. HTMX turns the interaction into two chunks of markup: one for the display UI and one for the edit UI, as shown in Listing 1.

Listing 1. The user update in HTMX


<div hx-target="this" hx-swap="outerHTML">
    <div><label>First Name</label>: Joe</div>
    <div><label>Last Name</label>: Blow</div>
    <div><label>Email</label>: joe@blow.com</div>
    <button hx-get="/contact/1/edit" class="btn btn-primary">
    Click To Edit
    </button>
</div>
<!-- The edit: -->
<form hx-put="/contact/1" hx-target="this" hx-swap="outerHTML">
  <div>
    <label>First Name</label>
    <input type="text" name="firstName" value="Joe">
  </div>
  <div class="form-group">
    <label>Last Name</label>
    <input type="text" name="lastName" value="Blow">
  </div>
  <div class="form-group">
    <label>Email Address</label>
    <input type="email" name="email" value="joe@blow.com">
  </div>
  <button class="btn">Submit</button>
  <button class="btn" hx-get="/contact/1">Cancel</button>
</form>

If you look at the markup in Listing 1, it is easy to see what is happening: the hx-swap property provides the HTML for the div before it is edited, and outerHTML tells the framework how it relates to the dynamic content inside. The editable version arrives as a form element that contains the x-put property, which identifies the PUT HTML method and what endpoint to use.

The question becomes, how does HTMX achieve this “swap” and subsequent PUT without doing any JavaScript? The answer is easy: it uses server-side rendering of the HTML for the edit markup and abstracts the form marshaling into the framework. The JavaScript is still working behind the scenes. Essentially, we get a more granular HTML syntax, which can just load segments instead of whole pages, and can submit Ajax requests. 

This is an interesting example of the DRY principle in action. Even with something like React, there’s a certain amount of boilerplate code that shuffles information from one form to another. Of course, HTMX hasn’t eliminated that entirely, but it has shifted the work onto the server.

Server-side HTMX

Now let's consider the server side of the equation. There are examples of many server-side technologies using HTMX because, as Gross says, HTMX is "back-end agnostic. It doesn't care what back end you use, so long as it produces HTML.”  To get a feel for how it works, let’s look at a TODO example that uses Express, along with the Pug HTML templating engine. This example is an implementation of the classic TODO application.

To start, the existing to-do items are output from Express with the command:


res.render('index', { todos: filteredTodos, filter, itemsLeft: getItemsLeft() });

This command uses the in-memory collection of to-dos and renders them with a Pug template that is in the typical format, except it includes the special hx- properties that drive HTMX interactions. For example, the form to POST new to-dos is shown in Listing 2.

Listing 2. Form POST with HTMX properties


form(hx-post="/todos", hx-target="#todo-list", hx-swap="afterbegin", _="on htmx:afterOnLoad set #txtTodo.value to ''")
  input#txtTodo.new-todo(name="todo",placeholder='What needs to be done?', autofocus='')

You can see here how the afterbegin attribute works to put the new content where it belongs in the list. The on htmx scripting is an example of Hyperscript, a kind of simplified scripting language. It is often used with HTMX, but isn’t strictly part of HTMX or required to use it. Essentially, on htmx is used here to handle setting the value of the input form after the new to-do has been created.

As another example, Listing 3 shows the Pug template for the to-do edit.

Listing 3. Editing the server-side template in PUG


form(hx-post="/todos/update/" + todo.id)
  input.edit(type="text", name="name",value=todo.name)

In Listing 3, the markup uses the hx-post property to indicate where to send the JSON for the edited to-do. The takeaway from these examples is what I noted earlier: the server is responsible for providing HTML (decorated with HTMX tags) in the right-sized chunks to fill the different parts of the screen that are required by the front end for its various interactions. The HTMX client will place them where they belong based on the properties, and will also handle sending the appropriate data for consumption by the services.

The endpoints responsible for receiving the data can operate like typical endpoints, with the distinction that the response should be the necessary HTMX. For example, in Listing 4, you can see how the Express server handles the POST to create a new to-do.

Listing 4. Handling to-do creation


app.post('/todos', (req, res) => {
  const { todo } = req.body;
  const newTodo = { 
    id: uuid(),
    name: todo, 
    done: false 
  };
  todos.push(newTodo);
  let template = pug.compileFile('views/includes/todo-item.pug');
  let markup = template({ todo: newTodo});
  template = pug.compileFile('views/includes/item-count.pug');
  markup  += template({ itemsLeft: getItemsLeft()});
  res.send(markup);
});

Listing 4 is a typical POST body handler, which takes the values off the form data and creates a new business object with it (newTodo). However, it then uses the values to populate the Pug template and send that back to the client to use as an insertion to the Todo list on the front end.

Other examples of server-side technology includes using HTMX along with Spring Boot with Thymeleaf in the Java world and Spring Boot with Django in the Python world.

Client-side templating with HTMX

A variation on this pattern that is supported by HTMX is using a client-side template. This is a layer that runs in the client and accepts JSON from the server, and does the markup translation there. When I asked Gross about using a RESTful service with JSON, he indicated it was possible with client-side templates, but with the caveat that REST is commonly misunderstood.

An inverse question, then, is how can we submit JSON to the server instead of the default form encoding? Once again, there’s an extension for that; namely, JSON-ENC

Conclusion

Contemplating HTMX causes a bundle of thoughts to arrive all at once. The upshot is that the notion is as beneficial as the project itself. HTMX as a mature project may not wind up working precisely as it does today, but it has already proved to be a helpful influence. The thing that is most compelling is the idea of handling all these very common Ajax-style requests, which would normally mean using fetch() or something similar, with just an HTML property. This is just simpler, cleaner, and keeps everything in one place. It’s very obvious what the markup does.

I’m more ambivalent about the server-side markup generation. Developers are so accustomed to dealing with JSON for this purpose; bringing in markup just adds a step to the client creation. We’ve seen numerous server-side approaches and they always seem to obfuscate the powerful trio of HTML, JavaScript, and CSS, which continues to win out in the end. Maybe this time will be different. It’s a big pendulum to swing.

Of course, there is the client-side templating option, which leaves the server as a familiar JSON emitter. I tried picturing how it would work in a big software project. Would it reduce overall complexity in a project at scale? 

Gross has his own thoughts on complexity. You can see his thoughts come to bear in the design of HTMX. This technology wants to simplify things by returning us to Hypertext as the state mechanism for web applications. This example shows the idea at work. Using JSON as the protocol means making clients smarter, more complex, and making the architecture less self-describing. 

Maybe it could all work. If we avoided the inherent complexity by expanding the underlying language, HTML, to actually handle modern requirements, like Ajax, we could return to a simpler time. The markup would once again be the central data descriptor, and sufficient to describe the UI, as well as the data on the wire.