htmx

HTMX – What is it and what can it do?

With htmx it is possible to control AJAX calls, CSS transitions, WebSockets and server sent events directly in HTML. HTML attributes are used for this. htmx is dependency-free and lightweight.

Even though htmx is designed to do as much as possible in HTML (via attributes), it also offers a number of JavaScript methods and events so that both (markup and functionality) can be coupled quite easily. htmx also offers a number of extensions that can also be integrated. It is also possible to create your own extensions and integrate them accordingly.

Before the various functionalities are discussed further in the lower part of the article, htmx will first be explained in more detail using a simple, small example.

Infinite scroll – An htmx example

A good and simple example of htmx is an infinite scroll – the loading of data when scrolling. This example does without a backend and only works with content from various HTML files. The starting point is the following folder structure:

htmx
  |-- infinite-scroll
  |  |-- contacts
  |  |  |-- page1.html
  |  |  |-- page2.html
  |  |  |-- page3.html
  |  |-- index.html

The index.html is the page that is displayed in the frontend. Below is the code for index.html, which is explained in more detail below:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HTMX</title>
    <script src="https://unpkg.com/htmx.org@1.9.11"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
      table {
        width: 800px;
        margin: 0 auto;
        margin-top: 85vh;
      }

      tr {
        height: 150px;
      }
    </style>
  </head>
  <body>
    <table>
      <tbody>
        <tr hx-get="/htmx/infinite-scroll/contacts/page2.html" hx-trigger="revealed" hx-swap="afterend">
          <td>Max Mustermann</td>
          <td>m.mustermann1@null.org</td>
          <td>1</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

As the code above shows, version 1.9.11 (the latest version at the time of writing this article) of htmx is included in this example.

There is a table with contact data in the body. The example is designed so that further contact data is loaded when scrolling, where only the number in the mail address and the number in the last column is increased (to make it clear that further table rows are actually loaded here and not the above-mentioned one is duplicated).

To enable an infinite scroll, there are some styles in the head of the page that cause the table to sit at the bottom of the viewport. This is because htmx is configured in such a way that the other data is loaded when the “trigger row” with the htmx attributes has reached the viewport. This is the case directly when the page is loaded, so that the other data from page2.html is also loaded directly. The data from page3.html and page4.html are then gradually loaded and displayed as you scroll down.

htmx attributes begin with hx, followed by the actual attribute (e.g. the request method “get” or the trigger property (“trigger”)). If you are concerned that this will be evaluated negatively in Lighthouse evaluations, you can also use “data” before hx, e.g. data-hx-get. This spelling is also supported by htmx.

The above table initially contains a row with data. Among other things, this has the attribute hx-get which ensures that a GET request is made to the page passed to the attribute as a value. In this simple example, an HTML page (page2.html) is requested directly. htmx offers different request methods, e.g. POST (hx-post) or PUT (hx-put).

Using hx-trigger=”revealed”, htmx is informed that the GET request should be started when the element (i.e. the tr) enters the viewport or is already in the viewport when it is loaded.

hx-swap is an attribute that is used very frequently in htmx. It indicates that content should be changed accordingly at this point. In other words, hx-swap tells htmx that the response to the previous request should be inserted at this point. There are different properties that can be passed as a value here. In this example, “afterend” is passed, which means that the response should be inserted after the end of the tr. In other use cases, other properties can be used which, for example, completely replace the element or place the response code within the element.

If you now compare the three attributes used and their respective values with a self-built, comparable functionality in JavaScript, you quickly recognize the lightweight nature of htmx. htmx makes it possible to send requests and manipulate the DOM with the responses using simple, fast means.

Loading further data

The previous section has already laid the foundation for loading the data from page2.html. Here is the code for this file:

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann2@null.org</td>
  <td>2</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann3@null.org</td>
  <td>3</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann4@null.org</td>
  <td>4</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann5@null.org</td>
  <td>5</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann6@null.org</td>
  <td>6</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann7@null.org</td>
  <td>7</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann8@null.org</td>
  <td>8</td>
</tr>
<tr hx-get="/htmx/infinite-scroll/contacts/page3.html" hx-trigger="revealed" hx-swap="afterend">
  <td>Max Mustermann</td>
  <td>m.mustermann9@null.org</td>
  <td>9</td>
</tr>

As can be seen, page2.html only contains tr elements with the other data. The last row has the same attributes as the first table row within index.html. The difference here, is that the GET request (hx-get) goes to page3.html.

This means that the data from page3.html is requested and inserted when the table row with Max Mustermann, m.mustermann9@null.org, 9, enters the viewport.

page3.html has the same structure and functions identically to page2.html, but it requests the data from page4.html.

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann10@null.org</td>
  <td>10</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann11@null.org</td>
  <td>11</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann12@null.org</td>
  <td>12</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann13@null.org</td>
  <td>13</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann14@null.org</td>
  <td>14</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann15@null.org</td>
  <td>15</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann16@null.org</td>
  <td>16</td>
</tr>
<tr hx-get="/htmx/infinite-scroll/contacts/page4.html" hx-trigger="revealed" hx-swap="afterend">
  <td>Max Mustermann</td>
  <td>m.mustermann17@null.org</td>
  <td>17</td>
</tr>

page4.html again contains tr elements with the last data:

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann18@null.org</td>
  <td>18</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann19@null.org</td>
  <td>19</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann20@null.org</td>
  <td>20</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann21@null.org</td>
  <td>21</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann22@null.org</td>
  <td>22</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann23@null.org</td>
  <td>23</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann24@null.org</td>
  <td>24</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann25@null.org</td>
  <td>25</td>
</tr>

There are no more hx attributes within page4.html. These are no longer required here, as this is the last “page” with table rows. Further data is no longer loaded and the end of the page has been reached.

As described, this is a very simple and straightforward example of the use of htmx. Since htmx also offers the possibility of manipulating the browser history, the above example could be supplemented in such a way that when each “page” is loaded, the history in the browser is manipulated with the corresponding requested url.

htmx also offers many more functionalities. Some of these are shortly mentioned in the following section to illustrate that infinite scroll is just a simple example and that there are many more use cases for htmx.

Further functions of htmx

AJAX request methods

htmx offers the following methods for AJAX requests:

  • GET (hx-get attribute)
  • POST (hx-post attribute)
  • PUT (hx-put attribute)
  • PATCH (hx-patch attribute)
  • DELETE (hx-delete attribute)

This allows htmx to use the most suitable method for the respective use case.

Various triggers

The hx-trigger attribute can be used to include a wide variety of triggers for requests. These can be mouse events, keyboard events, scroll events, load events or even intervals (e.g. hx-trigger=”every 2s”). You also have the option of combining triggers, delaying them with a delay (e.g. hx-trigger=”keyup changed delay:500ms”) or filtering them (hx-trigger=”click[ctrlKey]”).

Swap, target & synchronization

As already described in the example above, hx-swap is used to swap, insert or delete content in the DOM. Here, hx-swap not only offers the option of specifying what should happen to the content (e.g. hx-swap=”innerHTML” for insertion within the element with this attribute) – the swap can also be given further options which, for example, ensure that the viewport scrolls to the inserted element. As htmx can also be configured globally, it is also possible to set the transition of swaps, i.e. you can specify the transition with which newly inserted elements should be displayed.

An alternative to swapping is working with hx-target. This attribute can be given a CSS selector or, alternatively, extended selectors such as this, closest, next, previous, etc.. With hx-target you tell htmx that the data from the reponse should be further processed (e.g. displayed) in the target element. This is useful if, for example, the trigger and target elements are separated from each other in the markup due to the layout.

Requests and their triggers can be synchronized with each other using hx-sync. With nested requests, for example, you can stop a request when the other request is taking place. Example: A form with a hx-post=”/save” to save the form when the submit button is clicked. Within the form there is an input element with an hx-post=”/validate”, which performs a validation when changes are made. Now you can add hx-sync=”closest form:abort” to the input element and tell htmx that the request for validation should not take place if the request to save the form (by clicking on the submit button) has been triggered.

Boosting

The hx-boost attribute with the value “true” causes all a-tags within the element on which the attribute is located to be converted to AJAX requests. For example, if you have a link to a blog page in the content (href=”/blog”) and apply hx-boost=”true” to a surrounding element, the blog page is requested by htmx via AJAX and the result (the blog page) is inserted in the body tag of the current page.

WebSockets & Server sent events

With the hx-ws attribute, htmx offers the option of establishing a connection to a WebSocket. This is useful for a chat, for example. With simple means, you can send the user input to the chat backend and display the answers in a corresponding area.

You can also process server sent events with hx-sse. This means that a connection to the server can be established with simple means, which listens to events that are sent from the server to the browser. A good example of this is a news feed update.

And even more …

In addition, htmx offers many more possibilities. To even begin to mention all of them here would probably go beyond the scope. htmx offers, for example, attributes for validations, supports animations, offers JavaScript events and methods with which, the data of a response can be further processed, it offers 3rd party integrations and much more.

Conclusion

In conclusion, htmx is very feature-rich and lightweight at the same time. In addition, htmx can be extended with extensions for various use cases. It is certainly worth taking a closer look at htmx and considering it for your projects as an alternative or supplement to the “big players” in this field. You can find more information about htmx here: https://v1.htmx.org/

Overview of Web development