Skip to content

Response headers

The server can steer htmx from the response: change the target or swap style, update the browser URL, navigate, refresh, or fire events. Response.Htmx(...) gives you a fluent, typed way to set those HX-* headers from a Razor Page handler or an MVC action.

using Htmx;
Response.Htmx(h => h.PushUrl("/products").Retarget("#list").Reswap(HtmxSwap.OuterMorph));

HtmxHttpExtensions.Htmx(this HttpResponse response, Action<HtmxResponseHeaders> action) runs your lambda against an HtmxResponseHeaders writer. Every helper returns the same instance, so calls chain.

Helper Header Notes
PushUrl(string) HX-Push-Url Push a URL into history.
PreventPush() HX-Push-Url: false Suppress an hx-push-url push.
ReplaceUrl(string) HX-Replace-Url Replace the current URL, no new history entry.
PreventReplace() HX-Replace-Url: false Suppress a URL replace.
Redirect(string) HX-Redirect Full-page navigation (location.href).
Refresh() HX-Refresh: true Full page reload.
Retarget(string) HX-Retarget CSS selector that overrides the target.
Reswap(string) HX-Reswap Swap style (and modifiers), e.g. HtmxSwap.OuterMorph.
Reselect(string) HX-Reselect CSS selector choosing which part of the response to swap.
Location(string) HX-Location htmx-style navigation to a path, no full reload.
Location(HtmxLocation) HX-Location (JSON) Navigation with options.
WithTrigger(...) HX-Trigger See Triggering events.
WithVary() / WithVary(params string[]) Vary See Caching.

HtmxResponseHeaders.Keys holds the header-name constants (Location, PushUrl, Redirect, Refresh, ReplaceUrl, Reselect, Reswap, Retarget, Trigger); Keys.All returns them as string[]. See the response header reference.

// DemoController.cs — resp/{name}
Response.Htmx(h => h.PushUrl("/ResponseHeaders?pushed=1").WithVary());
Response.Htmx(h => h.PreventPush().WithVary("HX-Source", "HX-Target"));
Response.Htmx(h => h.ReplaceUrl("/ResponseHeaders?replaced=1"));
Response.Htmx(h => h.Redirect("/Boosted"));
Response.Htmx(h => h.Refresh());
Response.Htmx(h => h.Retarget("#resp-retarget-target"));
Response.Htmx(h => h.Reswap(HtmxSwap.BeforeEnd));
Response.Htmx(h => h.Reselect("#resp-pick"));
public IActionResult OnPost(ProductInput input)
{
if (!ModelState.IsValid)
{
// Show the form again in place,
// even if the button targeted something else.
Response.Htmx(h => h.Retarget("#product-form").Reswap(HtmxSwap.OuterHtml));
// 422 bodies swap by default in htmx 4
return Partial("_Form", input);
}
Response.Htmx(h => h.PushUrl("/products").WithTrigger("product-saved"));
return Partial("_Row", input);
}

Location("/path") sends the path as a plain string. Pass an HtmxLocation to also send options (serialized as JSON, null members omitted). The Path is required.

Location.cshtml.cs
Response.Htmx(h => h.Location(new HtmxLocation
{
Path = "/Location?handler=Landing",
Target = "#loc-dest",
Swap = HtmxSwap.InnerHtml,
Select = "#loc-incoming",
SelectOob = "#loc-oob-incoming:#loc-oob",
Values = new Dictionary<string, object> { ["from"] = "hx-location", ["n"] = 1 },
Headers = new Dictionary<string, string> { ["X-Demo"] = "loc" },
Transition = true
}));
Property Type JSON key
Path (required) string path
Source string? source
Event string? event
Target string? target
Swap string? swap
Select string? select
SelectOob string? selectOOB
Values IDictionary<string, object>? values
Headers IDictionary<string, string>? headers
Transition bool? transition

On the wire the header looks like HX-Location: {"path":"/Location?handler=Landing","target":"#loc-dest","swap":"innerHTML",...}.

String constants for Reswap(...), HtmxLocation.Swap, or your own markup.

Constant Value
InnerHtml innerHTML (default)
OuterHtml outerHTML
OuterSync outerSync
InnerMorph / OuterMorph innerMorph / outerMorph
BeforeBegin / Before beforebegin / before
AfterBegin / Prepend afterbegin / prepend
BeforeEnd / Append beforeend / append
AfterEnd / After afterend / after
Delete delete
None none (OOB swaps and headers still processed)
TextContent textContent

Reswap accepts modifiers too: Response.Htmx(h => h.Reswap(HtmxSwap.InnerHtml + " transition:true")). Morph swaps are covered in Partials and morph.

WithVary() appends HX-Request to Vary; WithVary("HX-Source", ...) appends the given names without duplicating existing values. Use it when one URL returns different content to htmx and non-htmx callers. Full discussion in Caching.

Response.StopPolling() (HtmxHttpExtensions.StopPolling) sent status 286. htmx 4 no longer honors it, so it is [Obsolete(error: true)]. There is no status-code replacement in htmx 4. Instead, a poller keeps going until the response replaces it with content that has no poller:

<div id="poll" hx-get="/demo/poll?n=0" hx-trigger="load delay:1s" hx-swap="outerHTML">0 polling</div>
DemoController.cs
[HttpGet("poll")]
public IActionResult Poll(int n = 0, int stop = 3)
{
n++;
// no hx-trigger in the reply => polling ends
if (n >= stop)
return Partial("_PollStopped", new PollModel(n));
// the reply re-arms the poll via hx-trigger="load delay:1s"
return Partial("_Polling", new PollModel(n, stop));
}

See also the migration guide for removed helpers.


See the live demos: run dotnet run --project samples/Htmx4.Sample and open /ResponseHeaders (source), /Location (source) and /Polling (source).

From the blog: HTMX and ASP.NET Core Swapping Techniques and Update HTML Elements with Htmx Triggers and ASP.NET Core.