HTMX - Exploring The Capabilities
Necati Özmen
Posted on November 22, 2023
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>
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>
Installing through npm: you can install HTMX through npm using the below command
npm install htmx.org
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";
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";
- Then add the code below to the file
window.htmx = require("htmx.org");
- 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>
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>
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>
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>
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.
-
root:
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
We then make an ajax request and replace the content with the content below:
<div id="div1" class="red">
The Republic
</div>
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;
}
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
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>
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>
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>
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>
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>
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>
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>
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>
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);
});
HTMX helpers:
htmx.on("htmx:load", function (evt) {
myJavascriptLib.init(evt.detail.elt);
});
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);
});
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
});
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");
}
});
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);
}
};
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.
Posted on November 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.