Ktor - HTMX

  • 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:

    ModuleDescription
    ktor-htmxCore definitions and header constants
    ktor-htmx-htmlIntegration with the Kotlin HTML DSL
    ktor-server-htmxRouting 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:

    AttributeDescription
    hx-getIssues a GET request to the given URL
    hx-postIssues a POST request to the given URL
    hx-putIssues a PUT request to the given URL
    hx-patchIssues a PATCH request to the given URL
    hx-deleteIssues 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:

    src/Routing.kt
    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:

    NameDescription
    innerHTMLthe default, puts the content inside the target element
    outerHTMLreplaces the entire target element with the returned content
    afterbeginprepends the content before the first child inside the target
    beforebeginprepends the content before the target in the target’s parent element
    beforeendappends the content after the last child inside the target
    afterendappends the content after the target in the target’s parent element
    deletedeletes 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>
    Task

    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.

    Pending