DEV Community

Cover image for HTMX - Exploring The Capabilities
Necati Özmen for Refine

Posted on • Originally published at refine.dev

HTMX - Exploring The Capabilities


refine repo


Author: Peter O.

Introduction

In this article, we will explore what HTMX is and its capabilities.

HTMX is a small (14k min. gzipped), dependency-free javascript library that can create cutting-edge user interfaces with the ease and power of hypertext (markup).
It provides access to AJAX, CSS Transitions, WebSockets, and Server Sent Events directly in HTML using attributes. This has become a game-changer for developers as it enables them to achieve interactivity (that JavaScript gives) from the markup alone.

Steps to follow in this article:

Installing HTMX

You can install HTMX using three methods:

Through A CDN: You can load HTMX to your website/application by including the CDN at the head of your HTML file.

<script
  src="https://unpkg.com/htmx.org@1.9.6"
  integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
  crossorigin="anonymous"
></script>
Enter fullscreen mode Exit fullscreen mode

N/B: While this is possible,it is not advisable to use CDNs in production environments.

You can download a copy of the HTMX source file intoyour website/application

Download htmx.min.js from unpkg.com, place it in the proper directory in your project, then include it with a script> tag as needed:

<script src="/path/to/htmx.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Installing through npm: you can install HTMX through npm using the below command

npm install htmx.org
Enter fullscreen mode Exit fullscreen mode

After installing, you'll need to use the proper tooling to utilize node_modules/htmx.org/dist/htmx.js or node_modules/htmx.org/dist/htmx.min.js).
For instance, you may package HTMX with certain extensions and project-specific code.

Setting up HTMX in Webpack: If you are using webpack to manage your javascript, firstly, Install htmx via your package manager (npm, yarn, pnpm) and then add the import to your index.js

import "htmx.org";
Enter fullscreen mode Exit fullscreen mode

If you want to use the global htmx variable, you can inject it into the window scope as follows:

  • Create a unique JS file.
  • Import this file into your index.js file.
import "path/to/my_custom.js";
Enter fullscreen mode Exit fullscreen mode
  • Then add the code below to the file
window.htmx = require("htmx.org");
Enter fullscreen mode Exit fullscreen mode
  • Finally, rebuild your application bundle(to reflect the changes)

Ajax Requests with HTMX

HTMX provides custom attributes that allow you to perform AJAX requests directly from the HTML:

Attribute Description
hx-post fires a post request to a given url
hx-get fires a get request request to a given url
hx-put fires a put request to a given url
hx-patch fires a patch request to a given url
hx-delete fires a delete request to a given url

Use case

<div hx-post="/messages">Put To Messages</div>
Enter fullscreen mode Exit fullscreen mode

from the code above the hx-post attribute triggers a post api call to the url "/messages"

Trigger Request

In HTMX, an element's natural event initiates AJAX requests. For instance, the onchange event triggers the input, select, and textarea; the onsubmit event triggers the form; and the onclick event triggers everything else.

However, HTMX offers a unique hx-trigger attribute for situations when you want to change the event that initiates the request:

<div hx-post="http://localhost:3000/submit" hx-trigger="mouseenter">
  Submit
</div>
Enter fullscreen mode Exit fullscreen mode

In the code above, only if the user's mouse hovers above the div will the GET request be made to the specified URL.

Trigger Modifiers

A trigger's behavior can also be altered by a few other modifications. Use the once modifier for the trigger, for instance, if you want a request to occur only once:

<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
  Submit
</div>
Enter fullscreen mode Exit fullscreen mode

Other trigger modifiers you can use are:
changed - Only send a request if the element's value has changed

delay:<time interval> - It specifies a delay period before making the request. The countdown is reset if the event occurs again.

throttle:<interval time> - it specifies a delay time before making the request,. In contrast to delay, if a new event happens before the time limit is reached, the event is deleted, and the request is triggered at the end of the time period.

from:<CSS Selector>- listen to the event of a different element.

to view more modifiers that HTMX supports, visit here

Trigger Filters

These are applied by surrounding a javascript expression in square brackets after the event name. If the expression evaluates to true, the event will be triggered; otherwise, it will not.

<div hx-get="/clicked" hx-trigger="click[ctrlKey]">
  Click the force
</div>
Enter fullscreen mode Exit fullscreen mode

htmx includes a few special events that can be used with hx-trigger:

  • load - fires once when the element is loaded for the first time.
  • revealed - fires when an element first scrolls into the viewport
  • intersect - fires when an element first intersects the viewport. This allows for two additional options:
    • root:<selector> - a root element CSS selector for intersection
    • threshold:<float> - a floating point number between 0.0 and 1.0 indicating the amount of intersection on which the event should be triggered.

If you have a more advanced use case, you may also utilize custom events to trigger requests.

Polling

you can use the every syntax with a timeframe (in seconds) with the hx-trigger property to make an element poll a specific URL:

<div hx-get="/get-star-wars-data" hx-trigger="every 2s"></div>
Enter fullscreen mode Exit fullscreen mode

Load Polling

"Load polling" is an additional method for doing polling in HTMLX. It involves an element specifying a load trigger and a delay, and then replacing itself with the response:

<div hx-get="/get-star-wars-data" hx-trigger="load delay:1s" hx-swap="starWarsDataHTML"></div>
Enter fullscreen mode Exit fullscreen mode

Request Indicators

Because the browser does not provide feedback when an AJAX request is made, it is often beneficial to notify the user that anything is happening. You may do this with HTMX by using the htmx-indicator class.

The opacity of any element with the htmx-indicator class is set to 0 by default, meaning that even though the element is present in the DOM, it is invisible.

A htmx-request class is added to an element when HTMX submits a request. When a child element with the htmx-indicator class on it is exposed to the htmx-request class, it will transition to an opacity of 1, displaying the indicator.

<button hx-get="/click">
    Click Me!
    <img class="htmx-indicator" src="/spinner.gif">
</button>
Enter fullscreen mode Exit fullscreen mode

Using the hx-indicator attribute and a CSS selector, you can add the htmx-request class to an alternative element if you wish:

<div>
  <button hx-get="/click" hx-indicator="#indicator">
    Click the force!
  </button>
  <img id="indicator" class="htmx-indicator" src="/spinner.gif" />
</div>
Enter fullscreen mode Exit fullscreen mode

Targets

You can load the response into an element other than the one that initiated the request using the hx-target attribute which accepts a CSS selector(a div, class, etc).

<input type="text" name="star-wars-input"
    hx-get="/star-wars-characters"
    hx-trigger="keyup delay:500ms changed"
    hx-target="#search-results"
    placeholder="Search..."
>
<div id="search-results"></div>
Enter fullscreen mode Exit fullscreen mode

In the example above, the hx-target attribute targets the #search-result id and populate request values to the div containing the id when the request is successful.

Swapping

You can swap HTML contents returned to the DOM in HTMX. The target element's innerHTML is automatically replaced with the content. This can be changed by applying any of the following values to the **hx-swap **attribute:

Name Description
innerHTML the content is inserted inside the target element by default.
outerHTML the returning content completely replaces the target element.
afterbegin content is prepended before the target's first child
beforebegin prepends the content of the target's parent element before the target.
beforeend adds the content after the target's final child.
afterend adds the content to the targets parent element after the target
delete removes the target element irrespective of the response
none does not include the response's content

an example of hx-swap in action

<div hx-get="/example" hx-swap="afterend">
  Get Some HTML & Append It
</div>
Enter fullscreen mode Exit fullscreen mode

In the code above, the div will make a request and append the returned contents behind the div.

Synchronization

When you make requests in two elements and you want one of them to take priority over the other or wait until the other element's request is fulfilled, HTMX has a hx-sync feature that can be helpful.

For example, let us paint a scenario of a race situation between a form submission and a validation request for a specific input:

<form hx-post="/submit-character">
    <input id="title" name="title" type="text"
        hx-post="/validate-character"
        hx-trigger="change"
    >
    <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

When the input is completed and the form is instantly submitted, two concurrent requests are sent to /validate-character and /submit-character without the use of hx-sync.

When hx-sync="closest form:abort" is applied to an input, the form will be watched for requests and the input request will be aborted if a form request is found or begins while the input request is being processed. This automatically solves the synchronization constraint.

<form hx-post="/submit-character">
    <input id="title" name="title" type="text"
        hx-post="/validate-character"
        hx-trigger="change"
        hx-sync="closest form:abort"
    >
    <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Additionally, htmx offers a programmatic method for cancelling requests: To stop any in-flight requests, you can send an element the htmx:abort event into a htmx.trigger function:

<button id="request-button" hx-post="/submit-character">
    Submit Character
</button>
<button onclick="htmx.trigger('#request-button', 'htmx:abort')">
    Cancel Submission
</button>
Enter fullscreen mode Exit fullscreen mode

CSS Transitions

HTMX lets you perform CSS Transitions without the need for javascript. we will illustrate this wwith an example:

<div id="div1">The Empire</div>
Enter fullscreen mode Exit fullscreen mode

We then make an ajax request and replace the content with the content below:

<div id="div1" class="red">
  The Republic
</div>
Enter fullscreen mode Exit fullscreen mode

From the code above, a new class red has been added to the new content. We can create a CSS transition from the previous state(without the class red) to the current one (with the class red) in this scenario:

.red {
  color: red;
  transition: all ease-in 1s;
}
Enter fullscreen mode Exit fullscreen mode

How HTMX handles transitions under the hood:
Before new content is added to a page, elements that match the id attribute of the current content are found. This happens when new content is received from a server. Before the swap takes place, the properties of the old content are replicated onto the new element if a match is found for it in the updated content. After that, the new content is replaced, but the old attribute values remain. The new attribute values are then switched in following a "settle" time (20 ms by default).

Out of Band Swaps

The hx-swap-oob attribute in the response html can be used to swap content from a response directly into the DOM using the id attribute:

<div id="message" hx-swap-oob="true">Dark Sidious</div>
Anakin Skywalker
Enter fullscreen mode Exit fullscreen mode

from the code above, The extra content would be swapped into the target in the typical way, but div#message would be swapped straight into the corresponding DOM element in this response.

Parameters

You can filter the arguments that will be submitted with an AJAX call using the hx-params attribute.

These are the possible values for this attribute:

  • "*"- Include every parameter by default
  • none - Do not include

  • <param-list> and do not include any parameters. All except the parameter names list

  • <param-list>, which should be comma-separated Provide a list of all the parameter names, separated by commas.

<div hx-get="/get-starwars-characters" hx-params="*">
  Luke Skywalker
</div>
Enter fullscreen mode Exit fullscreen mode

In the example above, All the parameters that a POST would contain are contained in this div, however as is customary with a GET, they will be URL encoded and included in the URL.

Confirming Requests

HTMX provides the hx-confirm attribute, which enables you to confirm an action using a straightforward javascript dialogue:

<button hx-delete="/delete-character" hx-confirm="Are you sure you wish to delete Obiwan Kenobi">
  Delete Obiwan Kenobi
</button>
Enter fullscreen mode Exit fullscreen mode

Web Sockets & SSE

HTMX currently has experimental supports for WebSockets and Server Sent Events. However, if you want to establish a WebSocket connection, you can use the hx-ws attribute

<div hx-ws="connect:wss:/darth-chatroom">
    <div id="chat_room">
        ...
    </div>
    <form hx-ws="send:submit">
        <input name="chat_message">
    </form>
</div>
Enter fullscreen mode Exit fullscreen mode

To send events to browsers using SSE(Server sent Events), add a connect <url> declaration to a parent element and add the hx-sse attribute with the URL.

After which, you will use the hx-trigger="sse:<event_name> attribute to define elements that are this element's descendants and are activated by server-sent events. grammar

A sample is shown below

<body hx-sse="connect:/darth-events">
  <div hx-trigger="sse:new_news" hx-get="/news"></div>
</body>
Enter fullscreen mode Exit fullscreen mode

History Support

With the hx-push-url attribute on an element you can push a request URL into the browser's navigation bar and add the page's current state to the history:

<a hx-get="/star-wars-characters" hx-push-url="true">
  Blog
</a>
Enter fullscreen mode Exit fullscreen mode

Principle on how the hx-push-url works:
Before making a request to /star-wars-characters, HTMX will take a snapshot of the current document and store it when a user clicks on this link. After that, a new location is added to the history stack and the swap is completed.

HTMX simulates "going back" to the prior state when a user presses the back button by retrieving the old content from storage and swapping it back into the target. HTMX will send an ajax call to the specified URL with the header HX-History-Restore-call set to true if the location cannot be located in the cache, and it will expect back the HTML required for the full page. Alternatively, it will force a hard browser refresh if the htmx.config.refreshOnHistoryMiss config setting is set to true.

Validation with HTMX

Because HTMX works with the HTML5 Validation API, an invalid validatable input will prevent a form request from being made. This applies to both WebSocket sends and AJAX queries.

Events related to validation in HTMX are:

htmx:validation:validate - called before an elements checkValidity() method is called. May be used to add in custom validation logic

htmx:validation:failed - called when checkValidity() returns false, indicating an invalid input

htmx:validation:halted - called when a request is not issued due to validation errors. Specific errors may be found in the event.detail.errors object

<form hx-post="/create-characters">
    <input _="on htmx:validation:validate
                if my.value != 'foo'
                    call me.setCustomValidity('Please enter the value foo')
                else
                    call me.setCustomValidity('')"
        name="example"
    >
</form>
Enter fullscreen mode Exit fullscreen mode

The code above is an illustration of an input using the htmx:validation. In the code, hyperscript was used to validate the event to ensure that an input have the value foo.

Animations with HTMX

HTMX is intended to allow you to add seamless animations and transitions to your web page using only CSS and HTML. You can now utilize the new View Transitions API to create animations with HTMX.
A basic example is the Fade Out On Swap animation.
If you want to fade out an element that will be removed when the request is finished, use the htmx-swapping class with some CSS and extend the swap phase to be long enough for your animation to finish. This can be accomplished as follows:

<style>
.fade-me-out.htmx-swapping {
  opacity: 0;
  transition: opacity 1s ease-out;
}
</style>

<button class="fade-me-out"
        hx-delete="/fade_out_demo"
        hx-swap="outerHTML swap:1s">
        Fade Me Out
</button>
Enter fullscreen mode Exit fullscreen mode

more examples of animations with HTMX are shown here

Extensions in HTMX

HTMX includes an extension framework for creating and deploying extensions within htmx-based applications.
With the hx-ext attribute You can utilize extension libraries(after being specified in javascript).

Using an extension entails two steps:

  • Include the extension definition, which will add it to the HTMX extension registry.
  • The hx-ext attribute is used to specify the extension.

An example is shown below:

// Including the extension library
<script src="/path/to/ext/debug.js" defer></script>

// add the extension(debug) to the hx-ext attribute
<button hx-post="/example" hx-ext="debug">
    This Button Uses The Force
</button>
Enter fullscreen mode Exit fullscreen mode

HTMX comes with a set of extensions that cover common developer needs. In each release, these extensions are tested against htmx. In order to view the list of included extensions in HTMX, visit here

Logging and events in HTMX

Events

HTMX includes a robust events mechanism that also serves as the logging system.

You can easily register to a HTMX event through:
The event listener approach:

document.body.addEventListener("htmx:load", function (evt) {
  myJavascriptLib.init(evt.detail.elt);
});
Enter fullscreen mode Exit fullscreen mode

HTMX helpers:

htmx.on("htmx:load", function (evt) {
  myJavascriptLib.init(evt.detail.elt);
});
Enter fullscreen mode Exit fullscreen mode

in both cases, htmx:load is the event.

The htmx:load event is fired whenever HTMX loads an element into the DOM, and it is functionally the same as the conventional load event.

You can also initialize a third-party library Using the htmx:load event:

htmx.onLoad(function (target) {
  myJavascriptLib.init(target);
});
Enter fullscreen mode Exit fullscreen mode

You can also configure a request with events. An example of this is shown below:

document.body.addEventListener("htmx:configRequest", function (evt) {
  evt.detail.parameters["auth_token"] = getAuthToken(); // add a new parameter into the request
  evt.detail.headers["Authentication-Token"] = getAuthToken(); // add a new header into the request
});
Enter fullscreen mode Exit fullscreen mode

In the code above, we used the htmx:configRequest event to change an AJAX request before it is sent:

You can adjust the swap behavior of HTMX by handling the htmx:beforeSwap event.

document.body.addEventListener("htmx:beforeSwap", function (evt) {
  if (evt.detail.xhr.status === 404) {
    // alert the user when a 404 occurs (maybe use a nicer mechanism than alert())
    alert("Error: Could Not Find Resource");
  } else if (evt.detail.xhr.status === 422) {
    // allow 422 responses to swap as we are using this as a signal that
    // a form was submitted with bad data and want to rerender with the
    // errors
    //
    // set isError to false to avoid error logging in console
    evt.detail.shouldSwap = true;
    evt.detail.isError = false;
  } else if (evt.detail.xhr.status === 418) {
    // if the response code 418 (I'm a teapot) is returned, retarget the
    // content of the response to the element with the id `teapot`
    evt.detail.shouldSwap = true;
    evt.detail.target = htmx.find("#teapot");
  }
});
Enter fullscreen mode Exit fullscreen mode

In the code above, we handled a few 400-level error response codes that would ordinarily not result in a swap in htmx.

In order to view more events that HTMX supports, visit here.

Logging

HTMX provides a htmx.logger variable which when set, logs every event. An example on how to set the logger is shown below:

htmx.logger = function (elt, event, data) {
  if (console) {
    console.log(event, elt, data);
  }
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this article, we provided an overview of the HTMX library. we showed that you can achieve Ajax functionality without relying on JavaScript with HTMX, allowing you to construct dynamic applications at ease.
HTMX may be more than just a new library. It may revolutionize the way we approach interactivity in web development.

Top comments (7)

Collapse
 
glntn101 profile image
Luke

Great article, thank you

Collapse
 
necatiozmen profile image
Necati Özmen

Thanks!

Collapse
 
tbroyer profile image
Thomas Broyer

Why does every article about htmx uses non-semantic HTML and has so much accessibility issues? I mean, most CSR app examples would use buttons here at a minimum. They wouldn't use forms most if the time but that less important because with broken/absent JS the app is just not there (blank page), but with htmx you're enhancing html and that means you just have a non-blank but entirely broken page.

(also, why is this post tagged as react and not with htmx?)

Collapse
 
dannyengelman profile image
Danny Engelman

if you have a hammer, everything starts to look like a nail

Collapse
 
slobodan4nista profile image
Slobi

After this comprehensive article I was motivated to try htmx for the first time and my impressions are great.
Minor issue I have when the hx-post ends with redirect in case of session ended the login page is shown in hx-target. Currently I embedded script at login page that sets href to login if not already on it. Tried other solutions with no luck :/

Collapse
 
ladyxena profile image
ladyxena

Great guide, thanks for sharing