Published on June 25, 2025
HTMX is a lightweight JavaScript library for building server-driven front ends by extending HTML with custom attributes. In contrast to other frameworks that recreate the existing app with their own syntax, HTMX provides a simpler grammar for building front ends by modifying the HTML of your pages with attributes rather than replacement components.
This guide explains what HTMX is, how it works, and why developers choose to use it. It also includes a tutorial with example code to get you started building your own HTMX apps.
HTMX is a single, frontend library that tries to avoid the need for you to learn a new architecture ideology by extending HTML itself. HTMX takes the approach that HTML is essentially incomplete, from the markup side of things. The core idea is to make HTML complete by providing a behind-the-scenes behavior to fill in where you might otherwise expect HTML to provide certain things. The behavior is integrated into your page via new attributes on the standard HTML elements.
Since it is just a single library, HTMX can also sit alongside other frameworks, meaning you can use it to add some light, partial updates to a portion of your existing application. Since you don’t need to learn many behaviors for interactivity, this makes reading and maintaining the client side logic much easier.
The biggest difference to other frontend options is to return HTML from your back end, rather than JSON or XML, etc. Once you get into the pattern, it’s actually quite easy to move forward, as you can store much of your page content as HTML template files — either partially or as whole pages.
HTMX allows you to render your HTML content into a specific element on the page in a controlled manner. Other popular frameworks do this by completely reinventing the frontend architecture and requiring you to break your application into components, which you need to learn as a separate library or discipline. HTMX attempts to make this part of its expanded HTML specification.
The documentation is also extensive and very complete, making examples easy to read, build, or implement when you’re preparing to extend your code.
You can clone our GitHub repository at /simple-htmx-node-backend to get up and running with this tutorial code immediately.
This tutorial shows you how to build a simple HTMX app project, containing a list of pages that each demonstrate one practical example powered by HTMX. This will use Node.js as the back end. The key difference to server-side rendered apps, or building apps that rely on REST APIs for their backend, is that you will need to return HTML rather than JSON or XML. Many examples are available from the HTMX project documentation.
Because HTMX requires no Node.JS build process for your frontend application, you can host your HTMX front end on static hosting for improved performance and cost effectiveness, then host your back end separately.
To get started, install Node.js.
Now, create a new folder for your HTMX app and navigate to it in your command line.
Next, initialize NPM in your project folder:
npm init -y
Install the Express package:
npm install express
Create the file server.js
and add this starter code to it:
Create a folder public
and in there add the file index.html
and add this initial content to it:
You now have a basic HTML web page. To add HTMX, simply add this script element to the <head>
element of your page:
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
Run your app with:
node server.js
Open this page in your browser: http://localhost:3000/
You should now see the text “HTMX with Node.js examples” on a plain page.
The examples that follow will all use the index.html
and server.js
files you just created; however, in the repository, you will find them under their own pages. For this tutorial, simply add the code to the existing files (the basic setup) and they will work.
Most HTML elements have so-called “natural” events, such as the <form>
element’s submit event. HTMX extends this model by extending these triggers both for any element and in any manner. For example, adding hx-get
to a DIV
element allows the DIV
to trigger the request, not just form submission elements like buttons.
Add this HTML to the index.html
file, right inside the <body>...</body>
element:
The button element is specifying that the code should make a GET
request to the /load-data
URL, and whatever is returned will replace the contents of the parent DIV
element, as defined by the hx-get
, hx-target
, and hx-swap
attributes.
To see this in action, add this code to the end of the server.js
file, and restart your Express app:
This is the simplest request listener for your Node.js server, which serves the static DIV
content.
Click the button and you’ll see the text in the button replaced with the text “New content loaded!”
Once you have your page designed and know where served content will be placed, you’ll want to design the requests themselves. The page makes a simple GET
request to the server at the URL relative to the current page. The request types supported by HTMX are:
hx-get
hx-post
hx-put
hx-patch
hx-delete
As with any HTML form, the values passed in the body message of the request will be the values of any field elements in the form.
Remove the code you just added to the two files above (index.html
and server.js
) and put this HTML into the index.html
file:
This is specifying that the form submission will make a post request to the server and use whatever comes back to replace the entire outer DIV
element.
Now, add this JavaScript to the server.js
file and restart your server again:
Refresh the page, enter your name into the text field, and click Submit. You will see that the server’s response completely replaces the content you just added to the page. This response includes the text you entered into the INPUT
element (or “Guest” if you entered nothing).
Remove the recently added HTML and JavaScript again and add this into the <body>
element of your index.html
file:
This is a little more complex. First, we have an item-container
, which will have any loaded content appended to the elements inside it. We can do this with the hx-target
and hx-swap
attributes.
The first content load is triggered by the hx-trigger
attribute specifying that the request should be made when the page initially completes loading in the browser.
You can modify triggers so that they only trigger under certain conditions or get limited in particular ways.
Now we have the server.js
to drop in:
This code defines some dummy content — just 1,000 text items labeled “Item X.”
The GET
listener gets the page index from the URL parameters, works out the page start and end from the page size (in this case 10, but it could be anything, including specified on the URL), and grabs a chunk of the items from the dummy content array.
It then renders (server-side) some HTML to be served and, if we’re not yet on the last page of dummy items, adds on an extra DIV element containing another hx-trigger
attribute. When scrolled into view, this final DIV
will cause the next page of content to load.
Again, restart your Express server, then refresh the browser and you will see content being loaded dynamically onto the page as you scroll down.
Auto-completion is a great feature to have, and HTMX makes it easy to implement. Drop this simple form onto your your index.html
page, replacing the previous content:
By now, this should be easy to read and pretty familiar, demonstrating just how little HTMX you actually need to get useful things built.
You’re making a GET
request to the /suggest
endpoint, but this time you’re waiting for the keyup
event to fire when the user releases a keyboard key in the INPUT
element. When this happens, a 300 millisecond delay occurs. If another key is not pressed in that time and the content of the INPUT
element has actually changed, HTMX makes a request to get content. The response is updated in the suggestions
DIV
.
This Node.js
logic will return the auto-complete suggestions:
This code simply takes the query parameter and searches in the dummy list of items for a match. It returns anything found as a simple list of text items containing DIV
elements.
For this one, you can check the repository for a little CSS to make the suggestion list more appealing, but only a little.
When creating an HTML page that will receive server-side content, the first step is to determine where that content will be placed and how it will be placed into — or how it will replace — content on the page. This is called targeting and swapping.
The default target for any request made by HTMX is the current element and the default swap is innerHTML
. This means that if you provide no alternative and use a button to request content, the returned content will replace the contents of the button, not the button element itself.
For tabbed navigation items, we’ll need this CSS dropped into the header of the index.html
file:
Now, add the tabs and the placeholder for their content to the <body>
element:
Again, this is quite simple, but we’re using three buttons to target the same DIV
element’s inner content with the HTML content loaded from the server at the unique tab’s URL. Here’s the logic to return that content for server.js
:
Of course, this is actually three separate request endpoints to serve each tab uniquely. The content for each is similarly simple, but it could be anything you want, including full HTML files containing more HTMX.
There are also a few HTMX-only CSS Selectors you can play around with, including:
this
– The same as not providing a target, it simply targets the element executing the request.
closest <selector>
– Finds the immediate parent element of the one executing the request, as long as the <selector>
matches that element.
find <selector>
– Finds the first child within the element executing the request, as long as the <selector>
matches it.
Finally, a little more styling to replace the previous example’s CSS in the index.html
file:
Now, replace the content dropped into the <body>
element with this DIV
:
Here, the page calls the server when the DIV
is clicked like a button. The content returned replaces the DIV
text content. Let’s see what the server returns.
This is the most complex server-side part yet. It assumes the default username “User123” and returns a new FORM
element.
The form specifies a PUT
request to another server endpoint, /update-username
, instead of the one just called. This shows you how dynamic the server-driven logic can be. As this is a PUT
request, it could also be defined under the same URL, but let’s not get carried away.
The form returned then shows an INPUT
element with the username and a button that lets you save that username back to the server.
Here’s the next event listener for the second endpoint address in this example. Drop this after the /edit-username
event listener above.
The endpoint logic takes the username provided by the user in the previously returned INPUT
element and serves a simple DIV
with that name. There’s nothing flashy going on here, so don’t be concerned when clicking the DIV
again and it appears to forget the entered username — we just haven’t coded it up to remember that.
The content above should get you in the right mindset for learning HTMX, and while we would love to cover everything HTMX has to offer in detail, it offers a truly huge number of capabilities.
Here are some areas to note when looking to improve your base implementation:
Indicators and UX enhancements: Placing the special CSS class htmx-indicator
on an element hides that element until a request is made. This lets you design user interfaces that show the user something while completing a potentially long request.
Polling and special events: The hx-trigger
attribute lets you specify the conditions under which the client should make a request. You can also delay
a request in seconds and milliseconds. You can trigger a request only when an element is scrolled into view on the page, and you can cause the client to repeat a request every given number of seconds.
Boost, navigation, and history: hx-boost
is a boolean attribute that will tell HTMX to make an AJAX request to the given URL but replace the entire page without navigating away from the current URL. This effectively hides the navigation. hx-push-url
is another boolean attribute that tells HTMX to add the request URL to the browser history, where hx-push-url
controls whether a URL goes into the history and hx-replace-url
updates the current URL in the browser history.
The learning curve with HTMX is shallow and the library is nimble enough that you can begin adding HTMX to your existing project or begin building a Contentful-powered HTMX web app from scratch.
HTMX supports extensions, one of which is the json-enc
extension, which lets your HTMX front end access your Contentful content, allowing you to find your content without any additional friction.
Because HTMX friction with other libraries and frameworks is so low, you can use it alongside the Contentful JS SDK and still leverage third-party SaaS offerings to reduce the development load by providing prebuilt, tested, and customizable functionality.
Subscribe for updates
Build better digital experiences with Contentful updates direct to your inbox.