How to Use htmx Integration for Partial Page Updates in Topcoat
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 (lines 31-35), inject the script like this:
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 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:
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 (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 that implement IntoResponseParts, automatically converting to HX-* headers. These include:
- HxRetarget: Changes which element receives the swap
- HxReswap: Modifies the swap strategy (e.g.,
innerHTMLvsouterHTML) - 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:
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:
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 file in the repository demonstrates a full implementation combining layout detection, fragment rendering, and response headers:
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 fromtopcoat::htmxto branch between full page and fragment rendering. - Control swapping behavior by returning responder types like
HxRetargetandHxReswapthat setHX-*response headers. - Trigger client events using
HxResponseTriggerto fire JavaScript callbacks after DOM updates. - Reference implementation is available in
examples/htmx/src/main.rsand documented incrates/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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →