Write web pages with network interaction without using a line of JavaScript — unless you want to.
endif; ?>
htmx is a simple way to add rich interactivity to web pages without writing JavaScript. On an htmx-powered page, you can imbue buttons, form elements, links, and other controls with behaviors just by adding htmx’s custom attributes. With htmx, you can “wire up” web pages that need “just enough” dynamic functionality with minimal effort.
htmx also gives you open-ended control. After you build a page with htmx, you can further extend its functionality with custom-written JavaScript if you need to. But its appeal comes from being able to add the most common kinds of interactivity to a page without having to do much heavy lifting.
htmx setup and basic syntax
To install htmx, you begin by adding a <script> tag in your page’s <head> block that loads htmx, either from a CDN or a local resource.
To use htmx, you add custom htmx attributes to the controls you want to wire up:
<form>
<input name="name" value="Rick Deckard">
<input name="email" value="deckard@br.lapd.gov">
<button hx-post="/report">File report</button>
</form>
All custom htmx attributes start with hx-. You don’t need to do anything other than add hx- attributes to elements to wire them up.
In the code above, adding hx-post to the button control means, “Clicking this button will submit a POST request to the endpoint /report.” The contents of the form block will be submitted along with that POST, as would normally happen with an HTML form.
Unlike a conventional HTML form submission, the entire page doesn’t get refreshed. The POST action is sent asynchronously via a JavaScript fetch action, so the page’s state is preserved. This is one of htmx’s biggest advantages. You can make use of this modern, convenient way to send data from a web page without having to roll the code to do it yourself.
For most htmx uses, you don’t need to write any JavaScript at all. However, you can wire up completely custom events via JavaScript after the fact, and make an htmx-powered page into something more advanced if need arises. (More on this later.)
Requests and responses with htmx
Some of the common behaviors you’d add to a web page with htmx include:
- Making a request to a remote server from a page, via a form submission or other action (as above).
- Taking the response from the server for such a request and displaying it somewhere.
- Performing other manipulations of the DOM as part of the above.
Instead of having to write JavaScript, htmx allows you to handle the vast majority of these kinds of basic, boilerplate actions with nothing more than markup.
Let’s take the above form example, and tweak it a little. We want to get the result back from the server, and display it in a div. Here’s how that would look:
<form>
<input name="name" value="Rick Deckard">
<input name="email" value="deckard@br.lapd.gov">
<button hx-post="/report" hx-target="#output" >
File report
</button>
</form>
<div id="output"></div>
The attribute hx-target uses a CSS selector to indicate where the server response is to be inserted in the page. In this case, it’s the div with the ID output.
Another common thing you might want to do is send feedback to the user that a request is taking place, so that requests that take a long time to respond don’t leave the user in the lurch. You can create an element with the htmx-indicator class that will be automatically revealed and then hidden when a request goes through. If you want to specify a particular element for your indicator, you can add the hx-indicator attribute on your action element, regardless of its class or styling.
The code below illustrates all of the above. We have hx-indicator on our button to note which element serves as the indicator, and the #indicator image with the class htmx-indicator to be automatically revealed. We could use this to have multiple indicators on the page to show feedback for different activities.
<form>
<input name="name" value="Rick Deckard">
<input name="email" value="deckard@br.lapd.gov">
<button hx-post="/report" hx-target="#output" hx-indicator="#indicator">
File report
</button>
</form>
<img id="indicator" class="htmx-indicator" src="/loading.gif" alt="Loading.">
<div id="output"></div>
Event triggers with htmx
Normally for a control, the trigger for any events is the default behavior. For instance, for a button, the trigger would be clicking that button (i.e., firing its click event).
However, you can modify triggers on a control by adding an hx-trigger attribute:
<button hx-post="/report" hx-target="#output"
hx-trigger="click[shiftKey]" hx-indicator="#indicator">
File report
</button>
Here, the button will fire on a click, but only if the Shift key is held down at the same time. This might be a good way to prevent a destructive action from accidentally taking place, like a delete.
Other htmx trigger modifications include delaying or throttling the triggered event, firing an event only once, firing an event on a regular interval, firing an event when it scrolls into view, or combinations of the above. For instance, using load delay:1s causes the event to fire one second after loading.
You can also listen for events from another element. For instance, you could have more than one button on a page trigger a given event, or everything of a certain CSS class.
Targeting elements with htmx
I noted above how you can use the hx-target attribute to declare where the content of a response gets placed. You can also use extended selectors to locate a target instead of using a specific ID or CSS selector. Some examples:
next/previous: The next or previous “sibling” of a CSS-selected element in the DOM to the target. For instance,next .outputwould locate the next item with the classoutput.find: Used with a CSS selector to find the first matching element. For instance,first .outputwould locate the first item with the class.output.closest: Likefind, but locates the matching ancestor element that is closest to the element where you’re usinghx-target.
You can also use body, document, and window to target those elements for a response.
Swapping elements with htmx
The default way to insert a response into a target is to replace the target’s inner HTML. But htmx gives you control over that behavior, too. Add an hx-swap attribute to the component performing the action, and you can override that behavior in a number of ways. For example:
outerHTMLreplaces the entire element with the content.before/afteradds the response before or after the target.prepend/appendprepends or appends the response inside the target. (This is a handy way to create a list of items incrementally as you fetch them.)textContentsets the text of the target to the response, without parsing any HTML in the response.
Link boosting with htmx
On many pages you might have a construction like this, such as in a navbar:
<div>
<a href="/start">Start here</a>
<a href="/logout">Log out</a>
</div>
With htmx, you can use the hx-boost tag to convert those links into GET requests that automatically swap the response into the <body> of a page:
<div hx-boost:inherited="true">
<a href="/start">Start here</a>
<a href="/logout">Log out</a>
</div>
“Boosted” links like this make transitions between pages less disruptive and reduce the amount of reparsing that needs to be performed (you don’t need to reload all the page’s JavaScript libraries). Plus, you can use CSS transitions for interesting effects.
Note that this feature comes with some downsides. For one, it deliberately doesn’t reset the state of the page, so you would need to manually reset any state on a transition.
Streaming responses with htmx
If you want to retrieve responses incrementally from the server, such as via a websocket, htmx offers several mechanisms for this depending on how you’re returning the response.
- Server-sent events: Use the
hx-sseextension, which requires little to no modification of existing htmx. However, you must return your response from the server with the headerContent-Type: text/event-streamforhx-sseto do anything with the response. - Multipart: If you send responses from the server using a
multipart/mixedresponse, you can use thehx-multipartextension to automatically parse each part as it arrives and add it incrementally to your page. - Websockets: This requires the most work in htmx to be useful. You need to install the
hx-wsextension and use a few otherhx-attributes, as in the example below.
<div hx-ws:connect="/chatroom" hx-target="#msgs" hx-swap="append">
<div id="msgs"></div>
<form hx-ws:send><input name="msg"><button>Post</button></form>
</div>
hx-ws:connect describes the endpoint to read from and send to. hx-ws:send is used on the form where you have the controls that provide the data to be sent.
htmx extensions
You’ve probably noticed by now that htmx is extensible through pre-written extensions. Most workaday functionality doesn’t need them, but some of the extensions can make working with htmx more pleasant overall. For example:
hx-browser-indicator: When you make an htmx request, this extension activates the “spinner” for the browser tab that normally shows a pending page load. This way, you give the user a common piece of visual feedback that a request has been fired and is on its way.hx-history-cache: This extension replaces htmx’s own browser history handling with cached data held in the browser’ssessionStorage. This way, browsing back causes the cached data to be displayed immediately, instead of forcing another network request (which might return changed information!).hx-download: This extension saves the response returned from the server to a file instead of inserting it into the page.hx-alpine-js: This extension ensures that any Alpine.js components on a page don’t get mangled when htmx makes updates.
Using htmx with JavaScript
An htmx-powered page doesn’t have to be powered only by htmx. If you want to add your own custom JavaScript interactivity, you can hook into htmx events with vanilla JavaScript.
For instance, if you want to attach a callback for every instance where htmx loads new content, you can hook into the onLoad method. If you manually add content to a page, you can use the process method.
htmx caveats
Using htmx comes with potential issues. The first is that you generally experience the best results wiring up a page that doesn’t already include automation, because that makes the interactions easier to reason about.
Fragments that include htmx markup are automatically wired up when they’re inserted into a page. However, anything you load or add manually, such as by invoking fetch() on your own, is not wired up. You will need to call htmx.process() to wire up manually added fragments.
Finally, htmx expects server responses to be HTML fragments, not JSON, by default. If you’ve been using a front-end framework that works with JSON, and your back end’s APIs return JSON, you’ll need to hook into the htmx:after:request event and intercept the response.


