This guide covers all HTMX response types available in acton-htmx, including both axum-htmx types and acton-htmx extensions.
HTMX works by sending HTTP headers that tell the client how to update the page. acton-htmx provides type-safe wrappers for these headers.
Redirect the browser to a new URL:
use acton_htmx::prelude::*;
async fn save_post() -> HxRedirect {
// Save post logic...
HxRedirect(axum::http::Uri::from_static("/posts"))
}HTMX Header: HX-Redirect: /posts
Use when: You want to redirect after a successful form submission.
Tell the browser to refresh the current page:
async fn delete_post() -> HxRefresh {
// Delete post logic...
HxRefresh
}HTMX Header: HX-Refresh: true
Use when: The current page needs to reload to reflect changes.
Trigger custom JavaScript events on the client:
use axum_htmx::HxResponseTrigger;
use serde_json::json;
async fn create_post() -> impl axum::response::IntoResponse {
// Create post logic...
HxResponseTrigger::normal(["postCreated"])
}
// With event data
async fn update_post(id: i64) -> impl axum::response::IntoResponse {
// Update post logic...
HxResponseTrigger::normal([("postUpdated", json!({"id": id}))])
}HTMX Header: HX-Trigger: postCreated or HX-Trigger: {"postUpdated": {"id": 42}}
Use when: You need to notify other parts of the page about changes.
Trigger Timing:
HxResponseTrigger::normal()- Trigger immediatelyHxResponseTrigger::after_settle()- Trigger after DOM settlesHxResponseTrigger::after_swap()- Trigger after content swap
Override the swap strategy for this response:
use axum_htmx::{HxReswap, SwapOption};
async fn update_nav() -> (HxReswap, Html<&'static str>) {
let swap = HxReswap(vec![SwapOption::OuterHTML]);
let html = Html("<nav>New navigation</nav>");
(swap, html)
}HTMX Header: HX-Reswap: outerHTML
Swap options:
InnerHTML- Replace inner HTML (default)OuterHTML- Replace entire elementBeforeBegin- Insert before elementAfterBegin- Insert at start of elementBeforeEnd- Insert at end of elementAfterEnd- Insert after elementDelete- Delete elementNone- Don't swap
Use when: You need fine-grained control over how content is inserted.
Override the target element for this response:
use axum_htmx::HxRetarget;
async fn show_error() -> (HxRetarget, Html<&'static str>) {
let target = HxRetarget("#error-container".to_string());
let html = Html(r#"<div class="error">Something went wrong</div>"#);
(target, html)
}HTMX Header: HX-Retarget: #error-container
Use when: You want to update a different element than specified in the request.
Select a specific part of the response to swap:
use axum_htmx::HxReselect;
async fn get_page() -> (HxReselect, Html<&'static str>) {
let reselect = HxReselect("#main-content".to_string());
let html = Html(r#"
<html>
<head><title>Page</title></head>
<body>
<div id="main-content">This will be swapped</div>
<footer>This will be ignored</footer>
</body>
</html>
"#);
(reselect, html)
}HTMX Header: HX-Reselect: #main-content
Use when: You want to return a full page but only swap part of it.
Push a new URL to the browser history:
use axum_htmx::HxPushUrl;
async fn load_post(Path(id): Path<i64>) -> impl axum::response::IntoResponse {
let post = load_post_from_db(id).await;
(
HxPushUrl(format!("/posts/{id}").parse().unwrap()),
post.render_html()
)
}HTMX Header: HX-Push-Url: /posts/42
Use when: You want the URL bar to reflect the current view.
Replace the current URL without adding to history:
use axum_htmx::HxReplaceUrl;
async fn filter_posts(Query(params): Query<FilterParams>) -> impl axum::response::IntoResponse {
let posts = filter_posts_from_db(params).await;
(
HxReplaceUrl(format!("/posts?category={}", params.category).parse().unwrap()),
posts.render_html()
)
}HTMX Header: HX-Replace-Url: /posts?category=rust
Use when: You want to update the URL without creating a history entry.
Navigate to a new location with optional parameters:
use axum_htmx::HxLocation;
use serde_json::json;
async fn submit_form() -> HxLocation {
HxLocation::from_uri("/success")
}
// With swap options
async fn navigate_with_options() -> HxLocation {
HxLocation::from_value(json!({
"path": "/posts/42",
"target": "#main",
"swap": "innerHTML"
}))
}HTMX Header: HX-Location: /success or HX-Location: {"path": "/posts/42", ...}
Use when: You need more control than a simple redirect.
Update multiple elements in a single response:
use acton_htmx::htmx::{HxSwapOob, SwapStrategy};
async fn update_post() -> impl axum::response::IntoResponse {
let mut oob = HxSwapOob::new();
// Update main content
oob.add(
"post-content",
"<article><h1>Updated Post</h1></article>",
SwapStrategy::InnerHTML
);
// Update notification badge
oob.add(
"notification-count",
"<span class=\"badge\">5</span>",
SwapStrategy::InnerHTML
);
// Update flash messages
oob.add(
"flash-messages",
r#"<div class="alert success">Post updated!</div>"#,
SwapStrategy::InnerHTML
);
oob
}HTML Output:
<div id="post-content" hx-swap-oob="innerHTML">
<article><h1>Updated Post</h1></article>
</div>
<div id="notification-count" hx-swap-oob="innerHTML">
<span class="badge">5</span>
</div>
<div id="flash-messages" hx-swap-oob="innerHTML">
<div class="alert success">Post updated!</div>
</div>Swap Strategies:
InnerHTML- Replace inner HTMLOuterHTML- Replace entire elementBeforeBegin- Insert before elementAfterBegin- Insert at startBeforeEnd- Insert at endAfterEnd- Insert after element
Use when: You need to update multiple page sections in one response.
You can combine multiple response headers:
async fn complex_update() -> impl axum::response::IntoResponse {
let content = Html("<div>Updated content</div>");
(
HxResponseTrigger::normal(["contentUpdated"]),
HxPushUrl("/posts/42".parse().unwrap()),
content
)
}Use HxTemplate trait for automatic partial/full page rendering:
use acton_htmx::prelude::*;
use askama::Template;
#[derive(Template)]
#[template(path = "posts/show.html")]
struct PostTemplate {
post: Post,
}
async fn show_post(
Path(id): Path<i64>,
HxRequest(is_htmx): HxRequest,
) -> impl axum::response::IntoResponse {
let post = load_post(id).await;
PostTemplate { post }.render_htmx(is_htmx)
}How it works:
- HTMX requests get just the
#main-contentsection - Regular browser requests get the full page with layout
See the Template Guide for details.
Return errors as HTMX responses:
use axum::http::StatusCode;
async fn save_post(form: Form<PostForm>) -> Result<HxRedirect, (StatusCode, Html<String>)> {
match validate_and_save(form).await {
Ok(_) => Ok(HxRedirect("/posts".parse().unwrap())),
Err(errors) => {
let html = render_errors(errors);
Err((StatusCode::UNPROCESSABLE_ENTITY, Html(html)))
}
}
}- Navigation: Use
HxRedirectorHxLocation - Refresh: Use
HxRefreshwhen the whole page changed - Notify: Use
HxTriggerto communicate with other elements - Update Multiple: Use
HxSwapOobfor multi-element updates
Update navigation, notifications, and flash messages alongside main content:
async fn create_post(form: Form<PostForm>) -> impl axum::response::IntoResponse {
let post = save_post(form).await;
let mut response = HxSwapOob::new();
// Main content
response.add("main-content", post.render_html(), SwapStrategy::InnerHTML);
// Flash message
response.add(
"flash-container",
r#"<div class="success">Post created!</div>"#,
SwapStrategy::InnerHTML
);
// Update post count
let count = get_post_count().await;
response.add(
"post-count",
&format!("<span>{count}</span>"),
SwapStrategy::InnerHTML
);
response
}Trigger events to update multiple independent components:
// In the handler
async fn update_cart(item: Form<CartItem>) -> impl axum::response::IntoResponse {
add_to_cart(item).await;
(
HxResponseTrigger::normal(["cartUpdated"]),
Html("<div>Item added</div>")
)
}<!-- In templates -->
<div id="cart-icon" hx-get="/cart/count" hx-trigger="cartUpdated from:body">
<span>🛒 0</span>
</div>
<div id="cart-total" hx-get="/cart/total" hx-trigger="cartUpdated from:body">
<span>$0.00</span>
</div>Use HxPushUrl for navigable content:
async fn show_tab(Path(tab): Path<String>) -> impl axum::response::IntoResponse {
let content = load_tab_content(&tab).await;
(
HxPushUrl(format!("/dashboard/{tab}").parse().unwrap()),
content.render_html()
)
}- Template Guide - Learn template integration
- Form Handling - Build validated forms with HTMX
- Examples - See complete working examples