Oxpecker.Htmx
4.0.0
See the version list below for details.
dotnet add package Oxpecker.Htmx --version 4.0.0
NuGet\Install-Package Oxpecker.Htmx -Version 4.0.0
<PackageReference Include="Oxpecker.Htmx" Version="4.0.0" />
<PackageVersion Include="Oxpecker.Htmx" Version="4.0.0" />
<PackageReference Include="Oxpecker.Htmx" />
paket add Oxpecker.Htmx --version 4.0.0
#r "nuget: Oxpecker.Htmx, 4.0.0"
#:package Oxpecker.Htmx@4.0.0
#addin nuget:?package=Oxpecker.Htmx&version=4.0.0
#tool nuget:?package=Oxpecker.Htmx&version=4.0.0
Oxpecker.Htmx
Oxpecker.Htmx extends Oxpecker.ViewEngine package with HTMX 4 attributes and headers.
This release targets htmx 4.0 (GA). It is a clean break from the htmx 2.x API — there are no compatibility shims or aliases for removed features.
Nuget package dotnet add package Oxpecker.Htmx
Each htmx attribute is exposed as a fluent extension method on HtmlTag, so attributes chain directly onto a tag with zero allocation overhead. The { children } builder syntax still works at the end of the chain:
open Oxpecker.ViewEngine
open Oxpecker.Htmx
let renderForm q =
form(action="/contacts", method="get") {
label(for'="search") { "Search Term" }
input(id="search", type'="search", name="q", value=q, style="margin: 0 5px", autocomplete="off")
.hxGet("/contacts")
.hxTrigger("search, keyup delay:200ms changed")
.hxTarget("tbody")
.hxPushUrl("true")
.hxIndicator("#spinner")
img(id="spinner", class'="spinner htmx-indicator", src="/spinning-circles.svg", alt="Request In Flight...")
input(type'="submit", value="Search").hxOn("click", "alert('clicked')")
}
Documentation
Please refer to the official HTMX 4 site for the documentation.
You can check ContactApp sample in the repository to get a better understanding of how the code will look like in your application.
API
Client side — Core attributes
After opening Oxpecker.Htmx namespace you'll get access to HTMX attributes:
- hxGet
- hxPost
- hxPut
- hxPatch
- hxDelete
- hxQuery
- hxSwap
- hxTarget
- hxTrigger
- hxSelect
- hxVals
Client side — Additional attributes
- hxSwapOob
- hxPushUrl
- hxInclude
- hxSelectOob
- hxBoost
- hxReplaceUrl
- hxConfirm
- hxDisable (htmx 4: disables elements during request, replaces old
hx-disabled-elt) - hxPreserve
- hxHeaders
- hxIndicator
- hxSync
- hxPreload (new in htmx 4)
- hxValidate
- hxEncoding
- hxAction (new in htmx 4)
- hxMethod (new in htmx 4)
- hxConfig (new in htmx 4, replaces old
hx-request) - hxIgnore (new in htmx 4, replaces old
hx-disable) - hxStatus (new in htmx 4)
- hxMorphSkip (morph swaps only)
- hxMorphSkipChildren (morph swaps only)
Client side — Event handler
- hxOn
Client side — Built-in extensions
htmx 4 ships a set of opt-in extensions that you load client-side with their own <script> tags. Open the Oxpecker.Htmx.Extensions namespace to get fluent extension methods for the attributes each one introduces:
open Oxpecker.Htmx
open Oxpecker.Htmx.Extensions
| Extension | Method(s) | Renders |
|---|---|---|
| hx-sse | hxSseConnect, hxSseClose |
hx-sse:connect, hx-sse:close |
| hx-ws | hxWsConnect, hxWsSend |
hx-ws:connect, hx-ws:send |
| hx-multipart | hxMultipartConnect, hxMultipartClose |
hx-multipart:connect, hx-multipart:close |
| hx-head | hxHead (see HxHeadMode) |
hx-head |
| hx-targets | hxTargets |
hx-targets |
| hx-ptag | hxPtag |
hx-ptag |
| hx-browser-indicator | hxBrowserIndicator |
hx-browser-indicator |
| hx-history-cache | hxHistory |
hx-history |
| hx-csp | hxNonce |
hx-nonce |
| hx-live | hxLive, hxLiveBind (see HxLiveBinding) |
hx-live, hx-live:{name} |
| hx-prompt | hxPrompt |
hx-prompt |
| hx-pending | hxPending |
hx-pending |
| hx-preload | hxPreload (lives in the core Oxpecker.Htmx namespace; the extension script is still required) |
hx-preload |
The hx-download and hx-upsert extensions reuse hxSwap. Use HxSwapMethod.download for downloads, and HxSwapMethod.upsert with the HxUpsertModifier helpers (sort, sortDesc, prepend, key) for upserts.
// hx-sse: persistent stream, append messages
div().hxSseConnect("/log").hxSwap("beforeend") { h3() { "Log:" } }
// hx-ws: connect + send a form over the socket
div().hxWsConnect("/chatroom") {
form().hxWsSend(true) {
input(name="message")
button(type'="submit") { "Send" }
}
}
// hx-targets: swap the response into every matching element
button().hxGet("/api/notification").hxTargets(".alert-box") { "Refresh All" }
// hx-download: save the response as a file
button().hxGet("/files/report.pdf").hxSwap(HxSwapMethod.download) { "Download" }
// hx-upsert: update-or-insert keyed list items, sorted descending
div().hxGet("/items").hxSwap(HxSwapMethod.upsert + HxUpsertModifier.sortDesc) { "items" }
// hx-multipart: stream parts from a persistent connection until a part fires the "done" event (HX-Trigger: done)
div().hxMultipartConnect("/events").hxMultipartClose("done") { "Waiting..." }
// hx-prompt: ask before deleting, answer arrives as the HX-Prompt request header
button().hxDelete("/item/1").hxPrompt("Reason?") { "Delete" }
// hx-pending: show a template while the request is in flight
form().hxPost("/message").hxTarget("#messages").hxSwap("beforeend").hxPending("#sending") {
input(name="body")
button() { "Send" }
}
// hx-live: bind an attribute to a reactive expression
button().hxLiveBind("disabled", "!q('#name').value") { "Save" }
// hx-live: special bindings — textContent and a single class toggle
span().hxLiveBind(HxLiveBinding.text, "q('#count').value")
div().hxLiveBind(HxLiveBinding.toggleClass "active", "q('#tab').value === 'a'") { "tab" }
Client side — Modifier helpers
Inheritable attributes accept an optional modifiers string that is appended verbatim after the attribute name — you must include the leading :. Pass htmx 4 modifier syntax directly, e.g. ":inherited", ":inherited:append", or for hxDisable also ":merge" / ":merge:inherited". The HxModifier module provides constants for the most common ones. hxStatus writes status-coded attributes (hx-status:CODE) and takes the code as its first argument.
// Explicit inheritance: renders hx-boost:inherited="true"
body().hxBoost("true", HxModifier.inherited) { ... }
// Inherited append: renders hx-include:inherited:append=".extra"
form().hxInclude(".extra", HxModifier.inherited + HxModifier.append) { ... }
// Merge: renders hx-disable:merge="find button"
main().hxDisable("find button", HxModifier.merge) { ... }
// Merge + inherited: renders hx-disable:merge:inherited="find button"
main().hxDisable("find button", ":merge:inherited") { ... }
// Status-code swap: renders hx-status:422="swap:innerHTML target:#errors"
form().hxPost("/save").hxStatus("422", "swap:innerHTML target:#errors") { ... }
// Wildcard status: renders hx-status:5xx="swap:none"
div().hxGet("/data").hxStatus("5xx", "swap:none") { ... }
Client side — Extended selectors
Build htmx 4 extended selectors in a type-safe way via the HxSelector module instead of writing them as plain strings. Pass the result into any selector-typed attribute (hxTarget, hxSelect, hxSelectOob, hxIndicator, hxInclude, hxDisable).
button().hxDelete("/item/1").hxTarget(HxSelector.closest ".card") { "Delete" }
div().hxGet("/user").hxTarget(HxSelector.find ".username") { "Loading" }
button().hxGet("/data").hxTarget(HxSelector.nextSibling) { "Load" }
button().hxGet("/data").hxTarget("#a, #b") { "Load" } // multiple targets: just write the comma-separated string
Available helpers:
- Values:
this',body,document,window,host,nextSibling,previousSibling - Functions:
closest,find,findAll,next,previous,global'
Server side — Request headers
- HxRequestHeader.Request
- HxRequestHeader.RequestType (new in htmx 4:
"full"or"partial") - HxRequestHeader.CurrentUrl
- HxRequestHeader.Source (new in htmx 4, replaces old
HX-Triggerrequest header) - HxRequestHeader.Target
- HxRequestHeader.Boosted
- HxRequestHeader.HistoryRestoreRequest
- HxRequestHeader.Accept (content types htmx accepts from the server)
- HxRequestHeader.LastEventId (last received SSE event ID for reconnection)
- HxRequestHeader.Preloaded (
hx-preloadextension) - HxRequestHeader.PTag (
hx-ptagextension) - HxRequestHeader.Prompt (
hx-promptextension; the value is URI-encoded, decode it withUri.UnescapeDataString) - HxRequestHeader.LastPartId (
hx-multipartextension)
Server side — Response headers
- HxResponseHeader.Trigger
- HxResponseHeader.Location
- HxResponseHeader.Redirect
- HxResponseHeader.Refresh
- HxResponseHeader.PushUrl
- HxResponseHeader.ReplaceUrl
- HxResponseHeader.Reswap
- HxResponseHeader.Retarget
- HxResponseHeader.Reselect
- HxResponseHeader.Download (
hx-downloadextension) - HxResponseHeader.PTag (
hx-ptagextension) - HxResponseHeader.PartId (
hx-multipartextension)
Migration from Oxpecker.Htmx 2.x
Removed attributes (no htmx 4 equivalent)
hxDisabledElt— usehxDisable(new semantics)hxDisinherit— not needed (inheritance is explicit in htmx 4)hxExt— include extension scripts directlyhxHistory— removed (no localStorage cache)hxHistoryElt— removedhxParams— usehtmx:config:requestevent insteadhxRequest— usehxConfig
Renamed attributes
- Old
hxDisable(skip htmx processing) →hxIgnore - Old
hxDisabledElt(disable form elements during request) →hxDisable
Moved to extensions
hxPromptandHxRequestHeader.Prompt— now provided by thehx-promptextension: openOxpecker.Htmx.Extensionsand load the extension script client-side
Removed request headers
HxRequestHeader.Trigger— useHxRequestHeader.SourceHxRequestHeader.TriggerName— useHxRequestHeader.Source
Removed response headers
HxResponseHeader.TriggerAfterSettle— useHxResponseHeader.TriggerHxResponseHeader.TriggerAfterSwap— useHxResponseHeader.Trigger
Migration from Oxpecker.Htmx 4.0.0-beta (htmx 4 beta4 → 4.0.0 GA)
Removed
hxOptimistic/ thehx-optimisticextension — replaced byhxPending(hx-pendingextension), which takes the same kind of template selectorHxRequestHeader.RequestId/HxResponseHeader.RequestId— thehx-wsextension was rewritten in GA and no longer usesHX-Request-ID
Added
hxQuery— the newQUERYverb (safe and idempotent likeGET, body-carrying likePOST)hxMorphSkip/hxMorphSkipChildren— opt elements out of morph swapsHxSwapModifier.swapEmpty— force or suppress the main swap when the response is empty after out-of-band content is removedhxPrompt(hx-promptextension) andHxRequestHeader.Prompt— htmx 2's prompt behaviour, restored as an extensionhxPending(hx-pendingextension)hxMultipartConnect/hxMultipartClose(hx-multipartextension), plusHxRequestHeader.LastPartIdandHxResponseHeader.PartIdhxLiveBind— thehx-live:{name}reactive attribute binding added to thehx-liveextension, withHxLiveBindingconstants for the specialtext,html,style,classand.{class}namesHxSwapModifier.settle/settleMsnow emit a leading space like every other swap modifier, so they compose with+directly; drop any manual+ " " +you had inserted
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Oxpecker.ViewEngine (>= 2.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.1.0 | 88 | 9/28/2026 |
| 4.0.0 | 350 | 9/2/2026 |
| 4.0.0-beta.5 | 524 | 6/11/2026 |
| 4.0.0-beta.4 | 79 | 5/31/2026 |
| 4.0.0-beta.3 | 327 | 5/1/2026 |
| 4.0.0-beta.2 | 80 | 4/29/2026 |
| 4.0.0-beta.1 | 86 | 4/28/2026 |
| 2.0.0 | 2,372 | 11/28/2025 |
| 1.0.1 | 482 | 8/12/2025 |
| 1.0.0 | 2,871 | 11/19/2024 |
| 0.1.2 | 621 | 8/13/2024 |
| 0.1.1 | 436 | 4/29/2024 |
| 0.1.0 | 408 | 3/1/2024 |
Aligned with htmx 4.0.0