HTMX
HTMX is a lightweight JavaScript library that enables dynamic client-side behavior using HTML attributes. It supports features such as AJAX, CSS transitions, WebSockets, and Server-Sent Events — without writing JavaScript.
htmx is a library that allows you to access modern browser features directly from HTML, rather than using
To understand htmx, first let’s take a look at an anchor tag:
<a href="/blog">Blog</a>This anchor tag tells a browser:
“When a user clicks on this link, issue an HTTP GET request to ‘/blog’ and load the response content into the browser window”.
With that in mind, consider the following bit of HTML:
<button hx-post="/clicked" hx-trigger="click" hx-target="#parent-div" hx-swap="outerHTML"> Click Me!</button>This tells htmx:
“When a user clicks on this button, issue an HTTP POST request to ‘/clicked’ and use the content from the response to replace the element with the id parent-div in the DOM”
Note that when you are using htmx, on the server side, you typically respond with HTML, not JSON. This keeps you firmly within the original web programming model, using Hypertext As The Engine Of Application State without even needing to really understand that concept.
Ktor
Ktor provides experimental, first-class support for HTMX through a set of shared modules that simplify integration in both server and client contexts. These modules offer tools for working with HTMX headers, defining HTML attributes using Kotlin DSLs, and handling HTMX-specific routing logic on the server.
Ktor’s HTMX integration provides:
- HTMX-aware routing for handling HTMX requests based on headers.
- HTML DSL extensions to generate HTMX attributes in Kotlin.
- HTMX header constants and values to eliminate string literals.
Ktor’s HTMX support is available across three experimental modules:
| Module | Description |
|---|---|
| ktor-htmx | Core definitions and header constants |
| ktor-htmx-html | Integration with the Kotlin HTML DSL |
| ktor-server-htmx | Routing support for HTMX-specific requests |
All APIs are marked with @ExperimentalKtorApi and require opt-in via @OptIn(ExperimentalKtorApi::class).
For more information, see HTMX integration
AJAX
The core of htmx is a set of attributes that allow you to issue AJAX requests directly from HTML:
| Attribute | Description |
|---|---|
hx-get | Issues a GET request to the given URL |
hx-post | Issues a POST request to the given URL |
hx-put | Issues a PUT request to the given URL |
hx-patch | Issues a PATCH request to the given URL |
hx-delete | Issues a DELETE request to the given URL |
Each of these attributes takes a URL to issue an AJAX request to.
The ktor-htmx-html module adds extension functions to Kotlin’s HTML DSL, allowing you to add HTMX attributes directly to HTML elements.
The ktor-server-htmx module provides HTMX-aware routing through the hx DSL block
Here’s an example Ktor route with and hx-get request:
route("hello") {
// Regular route (both HTMX and non-HTMX requests) get {
call.respondHtml { head { script { src = "https://unpkg.com/htmx.org@2.0.7" } } body { button { attributes.hx { get = "/hello" } +"Hello HTMX" } } } }
// Only matches HTMX requests (HX-Request header is present) hx.get { call.respondText("HTMX response!") }}HTMX-aware routing allow your application to respond differently depending on the HTMX headers sent by the client:
routing { route("api") { // Regular route (both HTMX and non-HTMX requests) get { call.respondText("Regular response") }
// Only matches HTMX requests (HX-Request header is present) hx.get { call.respondText("HTMX response") }
// Matches HTMX requests with specific target hx { target("#result-div") { get { call.respondText("Response for #result-div") } }
// Matches HTMX requests with specific trigger trigger("#load-button") { get { call.respondText("Response for #load-button clicks") } } } }}Target
If you want the response to be loaded into a different element other than the one that made the request, you can use the hx-target attribute, which takes a CSS selector.
body { button { attributes.hx { get = "/hello" target = "#hello-response" } +"Hello HTMX" } div { id = "hello-response" }}You can see that the results from the button are going to be loaded into div#hello-response, rather than into the button tag.
Swap
htmx offers a few different ways to swap the HTML returned into the DOM. By default, the content replaces the innerHTML of the target element. You can modify this by using the hx-swap attribute with any of the following values:
| Name | Description |
|---|---|
| innerHTML | the default, puts the content inside the target element |
| outerHTML | replaces the entire target element with the returned content |
| afterbegin | prepends the content before the first child inside the target |
| beforebegin | prepends the content before the target in the target’s parent element |
| beforeend | appends the content after the last child inside the target |
| afterend | appends the content after the target in the target’s parent element |
| delete | deletes the target element regardless of the response |
get { call.respondHtml { // ... button { attributes.hx { get = "/hello" target = "#hello-response" swap = HxSwap.beforeEnd } +"Hello HTMX" } ul { id = "hello-response" }}
hx.get { call.respondText("<li>Hello response</li>")}You can use the HxSwap object from the core ktor-htmx module to access constants for different HTMX swap modes.
Indicator
htmx-indicator is a special class that you can add to any element to indicate that an AJAX request is in progress.
When an AJAX request is issued, it is often good to let the user know that something is happening since the browser will not give them any feedback. You can achieve this in htmx by using htmx-indicator class.
The htmx-indicator class is defined so that the opacity of any element with this class is 0 by default, making it invisible but present in the DOM.
When htmx issues a request, it will put a htmx-request class onto an element (either the requesting element or another element, if specified). The htmx-request class will cause a child element with the htmx-indicator class on it to transition to opacity of 1, showing the indicator.
button { attributes.hx { get = "/hello" target = "#hello-response" swap = HxSwap.beforeEnd } +"Hello HTMX" img { src = "/resources/bars.svg" classes = setOf("htmx-indicator") alt = " Loading..." }}If you want the htmx-request class added to a different element, you can use the hx-indicator attribute with a CSS selector to do so:
<div> <button hx-get="/click" hx-indicator="#indicator"> Click Me! </button> <img id="indicator" class="htmx-indicator" src="/spinner.gif" alt="Loading..."/></div>Here we call out the indicator explicitly by id. Note that we could have placed the class on the parent div as well and had the same effect.
You can also add the disabled attribute to elements for the duration of a request by using the `hx-disabled-elt+ attribute.
button { attributes.hx { get = "/hello" target = "#hello-response" swap = HxSwap.beforeEnd disabledElt = "this" indicator ="#hello-indicator" } +"Hello HTMX"}
ul { id = "hello-response"}img { id = "hello-indicator" src = "/resources/bars.svg" classes = setOf("htmx-indicator") alt = " Loading..."}Trigger
By default, AJAX requests are triggered by the “natural” event of an element:
- input, textarea & select are triggered on the change event
- form is triggered on the submit event
- everything else is triggered by the click event
If you want different behavior, you can use the hx-trigger attribute to specify which event will cause the request.
Here is a div that posts to /mouse_entered when a mouse enters it:
<div hx-post="/mouse_entered" hx-trigger="mouseenter"> [Here Mouse, Mouse!]</div>You can use trigger attributes to implement many common UX patterns, such as Active Search:
<input type="text" name="q" hx-get="/trigger_delay" hx-trigger="keyup changed delay:500ms" hx-target="#search-results" placeholder="Search..."/><div id="search-results"></div>This input will issue a request 500 milliseconds after a key up event if the input has been changed and inserts the results into the div with the id search-results.
Implement this pattern in Ktor using htmx attributes in the HTML DSL.