How to Use HTMX for Partial Page Updates with Topcoat

Topcoat provides first-class HTMX integration through the topcoat::htmx module, offering ergonomic request helpers like hx_request() and type-safe response responders such as HxRetarget and HxReswap to handle partial page updates without writing manual header code.

The tokio-rs/topcoat framework streamlines the development of hypermedia-driven applications in Rust. By enabling the htmx feature, developers gain access to the topcoat-htmx crate, which provides a complete toolkit for detecting HTMX requests and controlling client-side swapping behavior through declarative response types.

Enable the HTMX Feature

To access the HTMX integration, add the htmx feature to your Cargo.toml. This activates the topcoat::htmx module and its associated request helpers and response types.

[dependencies]
topcoat = { version = "0.3.1", features = ["htmx"] }

The feature gates all HTMX-specific functionality in crates/topcoat/src/lib.rs, which re-exports the underlying topcoat-htmx crate.

Load the HTMX Script in Your Layout

Before handling HTMX requests, ensure your base layout includes the HTMX library. Use Topcoat's asset helper to reference either a CDN version or a self-hosted file.

use topcoat::{asset::asset, router::{Slot, layout}, view::view};

#[layout]
async fn root(slot: Slot<'_>) -> topcoat::Result {
    view! {
        <!DOCTYPE html>
        <html>
            <head>
                <script src=(asset!("https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js"))></script>
            </head>
            <body>(slot.await?)</body>
        </html>
    }
}

This pattern, documented in crates/topcoat/docs/htmx.md, ensures HTMX is available for all routes using this layout.

Detecting HTMX Requests

When HTMX initiates a request, it sends specific HX-* headers such as HX-Request, HX-Target, and HX-Boosted. Topcoat exposes ergonomic accessor functions in the request context (&Cx) to read these headers without manual parsing.

Conditional Rendering Based on Request Type

The hx_request function determines whether the current request originated from HTMX. Use this in your layout to return only the page fragment for HTMX swaps while rendering the full document for standard navigation.

use topcoat::{
    context::Cx,
    htmx::hx_request,
    router::{Slot, layout},
    view::view,
};

#[layout]
async fn root(cx: &Cx, slot: Slot<'_>) -> topcoat::Result {
    // HTMX swaps only the target element → return just the fragment.
    if hx_request(cx) {
        return slot.await;
    }

    // Full page render for normal (non-HTMX) requests.
    view! {
        <html>
            <body>
                <nav>/* persistent navigation */</nav>
                <main>(slot.await?)</main>
            </body>
        </html>
    }
}

Additional helpers like hx_boosted and hx_target are available in crates/topcoat-htmx/src/lib.rs for inspecting other HTMX headers.

Controlling HTMX Responses

Topcoat implements the IntoResponseParts trait for various HTMX-specific types, allowing you to set response headers by including them in your handler's return tuple. These responders control how HTMX processes the returned HTML on the client side.

Retargeting and Reswapping Elements

Use HxRetarget to change the element where the response is inserted, and HxReswap to modify the swapping strategy (e.g., innerHTML vs outerHTML).

use topcoat::{
    context::Cx,
    htmx::{HxRetarget, HxReswap, SwapOption},
    router::route,
    view::{View, view},
};

#[route(POST "/save")]
async fn save(cx: &Cx) -> topcoat::Result<(HxRetarget, HxReswap, View)> {
    let fragment = view! { <div>"Saved!"</div> }?;
    Ok((
        // Tell HTMX where to place the response.
        HxRetarget::from("#status"),
        // Override the default swap behavior.
        HxReswap(SwapOption::InnerHtml),
        fragment,
    ))
}

This approach, defined in crates/topcoat-htmx/src/lib.rs, eliminates the need to manually construct HX-Retarget or HX-Reswap headers.

Triggering Client-Side Events

Server handlers can trigger JavaScript events on the client after the swap completes using HxResponseTrigger and HxEvent.

use topcoat::htmx::{HxEvent, HxResponseTrigger};

fn trigger_toast() -> topcoat::Result<HxResponseTrigger> {
    // HX-Trigger-After-Swap: {"show-toast":"Saved!"}
    let trigger = HxResponseTrigger::after_swap([
        HxEvent::with_data("show-toast", "Saved!")?,
    ]);
    Ok(trigger)
}

This sets the HX-Trigger-After-Swap header with properly formatted JSON data, enabling your frontend to react to server-side state changes.

Core Implementation Details

The HTMX integration is implemented across two primary locations:

  • crates/topcoat-htmx/src/lib.rs – Contains the core implementation of request accessor functions (hx_request, hx_target, etc.) and responder types (HxRetarget, HxReswap, HxRedirect, HxRefresh, HxResponseTrigger).
  • crates/topcoat/docs/htmx.md – Provides comprehensive documentation and usage examples for both request handling and response generation.

These files demonstrate how Topcoat wraps raw HTTP headers in a type-safe Rust API, ensuring compile-time correctness for HTMX interactions.

Summary

  • Enable the htmx feature in Cargo.toml to activate the topcoat::htmx module and access HTMX-specific helpers.
  • Use hx_request(cx) in layouts to detect HTMX requests and return partial fragments instead of full page renders.
  • Return responder tuples like (HxRetarget, HxReswap, View) to control client-side swapping behavior without manual header manipulation.
  • Trigger client events with HxResponseTrigger and HxEvent to coordinate between server state changes and frontend JavaScript.
  • Reference crates/topcoat-htmx/src/lib.rs for the complete API surface including SwapOption and other configuration types.

Frequently Asked Questions

How do I detect if a request came from HTMX in Topcoat?

Call the hx_request function from topcoat::htmx and pass the request context (&Cx). This returns true when the HX-Request header is present, indicating the request originated from an HTMX element. The function is defined in crates/topcoat-htmx/src/lib.rs alongside related helpers like hx_boosted and hx_target.

What response headers can I set with Topcoat's HTMX integration?

Topcoat provides responder types for HX-Retarget, HX-Reswap, HX-Redirect, HX-Refresh, and HX-Trigger. These types implement IntoResponseParts, allowing you to include them in handler return tuples. For example, HxRetarget::from("#id") sets the target element, while HxReswap(SwapOption::InnerHtml) controls the insertion method.

How do I trigger JavaScript events from the server?

Use HxResponseTrigger::after_swap() combined with HxEvent::with_data() to create server-side events that fire after HTMX completes the swap. This generates the HX-Trigger-After-Swap header with properly escaped JSON data, which HTMX parses to dispatch events on the client.

Is the HTMX integration enabled by default?

No, you must explicitly enable the htmx feature in your Cargo.toml when declaring the topcoat dependency. This feature gates the topcoat-htmx crate re-export at topcoat::htmx, keeping the core library lightweight for applications that do not require HTMX support.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →