# How to Use htmx Integration for Partial Page Updates in Topcoat

> Easily implement partial page updates in Topcoat using htmx integration. Leverage request headers and response responders to swap HTML fragments without JavaScript.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat provides first-class htmx support through request header accessors like `hx_request` and response responders like `HxRetarget`, allowing you to return HTML fragments and control client-side swapping without writing JavaScript.**

The tokio-rs/topcoat framework ships with a dedicated `topcoat::htmx` module that eliminates the need for custom JavaScript when building dynamic web interfaces. By leveraging htmx's HTML-driven AJAX approach, you can implement **partial page updates**—where the server returns only the fragment that changed while htmx handles the DOM replacement. This integration works through three layers: loading the client library via the `asset!` macro, reading request headers to detect htmx traffic, and returning typed responders that set the appropriate `HX-*` response headers.

## Loading the htmx Script in Your Layout

Before handling partial updates, you must include the htmx library in your HTML. Topcoat's `asset!` macro allows you to reference CDN URLs or bundled assets directly in your layout views.

According to the documentation in [`crates/topcoat/docs/htmx.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/htmx.md) (lines 31-35), inject the script like this:

```rust
use topcoat::{asset::asset, router::layout, view::view, Result};

#[layout]
async fn root(slot: Result) -> 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?)</body>
        </html>
    }
}

```

## Detecting htmx Requests with Header Accessors

When htmx makes a request, it sends specific `HX-*` headers that Topcoat exposes through convenient accessor functions. These functions live in [`crates/topcoat-htmx/src/request.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-htmx/src/request.rs) and include `hx_request`, `hx_boosted`, and `hx_target`, all accepting a `&Cx` context reference.

To return only a fragment for htmx requests while rendering full pages for standard navigation, check the `hx_request` function in your layout:

```rust
use topcoat::{Cx, Result, htmx::hx_request, router::layout, view::view};

#[layout]
async fn root(cx: &Cx, slot: Result) -> Result {
    // If this request originated from htmx, render only the inner content.
    if hx_request(cx) {
        return slot;
    }

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

```

This branching logic—defined in the source at [`crates/topcoat-htmx/src/request.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-htmx/src/request.rs) (lines 30-33)—ensures your endpoints work for both full page loads and partial updates.

## Controlling Client Behavior with Response Responders

Topcoat provides typed responders in [`crates/topcoat-htmx/src/response.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-htmx/src/response.rs) that implement `IntoResponseParts`, automatically converting to `HX-*` headers. These include:

- **HxRetarget**: Changes which element receives the swap
- **HxReswap**: Modifies the swap strategy (e.g., `innerHTML` vs `outerHTML`)
- **HxLocation**: Client-side redirect
- **HxPushUrl**: Updates the browser history
- **HxRefresh**: Forces a full page reload
- **HxResponseTrigger**: Fires JavaScript events after swapping

Place these responders before your view in the returned tuple:

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

#[route(POST "/save")]
async fn save(cx: &Cx) -> Result<(HxRetarget, HxReswap, View)> {
    let body = view! { <div>"Saved!"</div> }?;
    Ok((
        // Swap the response into the element with id="status"
        HxRetarget::from("#status"),
        // Use innerHTML replacement instead of the default outerHTML
        HxReswap(SwapOption::InnerHtml),
        body,
    ))
}

```

The `HxReswap` type accepts variants like `SwapOption::InnerHtml` to control exactly how htmx replaces content, as implemented in the response module.

## Triggering Client-Side Events

To fire JavaScript events after htmx completes a swap, use `HxResponseTrigger` with `HxEvent` structures. This allows your Rust handlers to communicate with client-side scripts without direct JavaScript injection:

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

let trigger = HxResponseTrigger::after_swap([
    HxEvent::with_data("show-toast", "Saved!")?,
]);

```

This creates the appropriate `HX-Trigger` headers that htmx interprets after inserting the partial content.

## Complete Working Example

The [`examples/htmx/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/htmx/src/main.rs) file in the repository demonstrates a full implementation combining layout detection, fragment rendering, and response headers:

```rust
use topcoat::{
    Cx, Result,
    htmx::{hx_request, HxRetarget, HxReswap, SwapOption},
    router::{layout, route},
    view::{view, View},
};

#[layout]
async fn root(cx: &Cx, slot: Result) -> Result {
    if hx_request(cx) {
        return slot;
    }
    
    view! {
        <!DOCTYPE html>
        <html>
            <head>
                <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js"></script>
            </head>
            <body>
                <nav>My App</nav>
                <main>(slot?)</main>
            </body>
        </html>
    }
}

#[route(POST "/save")]
async fn save(_cx: &Cx) -> Result<(HxRetarget, HxReswap, View)> {
    let body = view! { <div class="alert">"Saved successfully!"</div> }?;
    Ok((
        HxRetarget::from("#notification-area"),
        HxReswap(SwapOption::InnerHtml),
        body,
    ))
}

```

This example shows how the same endpoint serves both full page renders and partial updates depending on the request headers, while the `POST` handler explicitly controls where the response fragment appears.

## Summary

- **Load htmx** using the `asset!` macro in your layout template, either from a CDN or bundled file.
- **Detect htmx requests** with `hx_request(&cx)` and other accessors from `topcoat::htmx` to branch between full page and fragment rendering.
- **Control swapping behavior** by returning responder types like `HxRetarget` and `HxReswap` that set `HX-*` response headers.
- **Trigger client events** using `HxResponseTrigger` to fire JavaScript callbacks after DOM updates.
- **Reference implementation** is available in [`examples/htmx/src/main.rs`](https://github.com/tokio-rs/topcoat/blob/main/examples/htmx/src/main.rs) and documented in [`crates/topcoat/docs/htmx.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/htmx.md).

## Frequently Asked Questions

### Do I need to enable a feature flag to use htmx in Topcoat?

Yes, the htmx integration is feature-gated. You must enable the `htmx` feature in your [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml) to access the `topcoat::htmx` module and its request/response types.

### Can I use htmx with Topcoat's `view!` macro?

Absolutely. The `view!` macro works seamlessly with htmx. You return `View` types from your handlers just as you would for full page renders, but you combine them with htmx responders like `HxRetarget` in a tuple to control client-side behavior.

### How does Topcoat handle htmx boosted requests?

Topcoat provides the `hx_boosted` accessor function that checks for the `HX-Boosted` header. Use this in your layout or handlers to detect when htmx is handling navigation via its boost feature, allowing you to skip re-rendering persistent layout elements.

### What swap options does HxReswap support?

`HxReswap` accepts variants from `SwapOption` including `InnerHtml`, `OuterHtml`, `BeforeBegin`, `AfterBegin`, `BeforeEnd`, and `AfterEnd`. These correspond directly to htmx's swap strategies, giving you fine-grained control over where the returned fragment inserts relative to the target element.