Falco.Htmx 1.2.0

dotnet add package Falco.Htmx --version 1.2.0
                    
NuGet\Install-Package Falco.Htmx -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Falco.Htmx" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Falco.Htmx" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Falco.Htmx" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Falco.Htmx --version 1.2.0
                    
#r "nuget: Falco.Htmx, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Falco.Htmx@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Falco.Htmx&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Falco.Htmx&version=1.2.0
                    
Install as a Cake Tool

Falco.Htmx

NuGet Version build

open Falco.Markup
open Falco.Htmx

let demo =
    _button
        [ Hx.get "/click-me"
          Hx.swapOuterHtml
          Hx.targetCss "#wrapper" ]
        [ _text "Reset" ]

Falco.Htmx brings type-safe htmx support to Falco. It provides a complete mapping of all attributes, typed request data and ready-made response modifiers.

Key Features

  • Idiomatic mapping of htmx attributes (i.e., hx-get, hx-post, hx-target etc.).
  • Typed access to htmx request headers.
  • Prepared response modifiers for common use-cases (i.e., HX-location, HX-Push-Url).
  • Built-in support for template fragments.

Design Goals

  • Create a self-documenting way to integrate htmx into Falco applications.
  • Match the specification of htmx as closely as possible, ideally one-to-one.
  • Provide type safety without over-abstracting.

Getting Started

This guide assumes you have a Falco project setup. If you don't, you can create a new Falco project using the following commands. The full code for this guide can be found in the Hello World example.

> dotnet new web -lang F# -o HelloWorld
> cd HelloWorldApp

Install the nuget package:

> dotnet add package Falco
> dotnet add package Falco.Htmx

Remove any *.fs files created automatically, create a new file named Program.fs and set the contents to the following:

open Falco
open Falco.Htmx
open Falco.Markup
open Falco.Routing
open Microsoft.AspNetCore.Builder

let bldr = WebApplication.CreateBuilder()
let wapp = bldr.Build()

let endpoints =
    [
    ]

wapp.UseRouting()
    .UseFalco(endpoints)
    .Run()

Now, let's incorporate htmx into our Falco application. First we'll define a simple route that returns a button that, when clicked, will swap the inner HTML of a target element with the response from a GET request.

let handleIndex : HttpHandler =
    let html =
        _html [] [
            _head [] [
                _script [ _src_ HtmxScript.cdnSrc ] [] ]
            _body [] [
                _h1' "Example: Hello World"
                _button
                    [ Hx.get "/click"
                      Hx.swapOuterHtml ]
                    [ _text "Click Me" ] ] ]

    Response.ofHtml html

Next, we'll define a handler for the click event that will return HTML from the server to replace the outer HTML of the button.

let handleClick : HttpHandler =
    let html =
        _h2' "Hello, World from the Server!"

    Response.ofHtml html

And lastly, we'll make Falco aware of these routes by adding them to the endpoints list.

let endpoints =
    [
        get "/" handleIndex
        get "/click" handleClick
    ]

Save the file and run the application:

> dotnet run

Navigate to https://localhost:5001 in your browser and click the button. You should see the text "Hello, World from the Server!" appear in place of the button.

htmx Attributes

hx-[get|post|put|patch|delete]

_button [ Hx.put "/messages" ] [
    _text "Put to Messages" ]

hx-trigger

_div [ Hx.post "/mouse-enter"; Hx.trigger "mouseenter" ] [
    _text "Here mouse, mouse!" ]

// Trigger modifiers
_div [ Hx.post "/mouse-enter"; Hx.trigger ("mouseenter", [HxTrigger.Once]) ] [
    _text "Here mouse, mouse!" ]

// Trigger filters
_div [ Hx.post "/mouse-enter"; Hx.trigger ("mouseenter", [HxTrigger.Once], "ctrlKey") ] [
    _text "Here mouse, mouse!" ]

hx-target

_form [] [
    _input [ Hx.get "/search"; Hx.target "#search-results" ]
]
_div [ _id_ "search-results" ] []

hx-swap

_button [ Hx.post "/like"; Hx.swapOuterHtml ] [
    _text "Like" ]

hx-swap-oob

_div [ _id_ "message"; Hx.swapOobOn ] [
    _text "Swap me directly" ]

// Equivalent to:
_div [ _id_ "message"; Hx.swapOob HxSwap.OuterHTML ] [
    _text "Swap me directly" ]

// With a selector:
_div [ _id_ "message"; Hx.swapOob (HxSwap.InnerHTML, "#falco") ] [
    _text "Swap me directly" ]

hx-select

_button [ Hx.get "/info"; Hx.select "#info-detail"; Hx.swapOuterHtml ] [
    _text "Get Info" ]

hx-select-oob

_div [ _id_ "alert" ] []
_button [ Hx.get "/info"; Hx.select "#info-detail"; Hx.swapOuterHtml; Hx.selectOob "#alert" ] [
    _text "Get Info" ]

hx-boost

_div [ Hx.boostOn ] [
    _a [ _href_ "/blog" ] [ _text "Blog" ] ]

hx-push-url

_div [ Hx.get "/account"; Hx.pushUrl true ] [
    _text "Go to My Account" ]

// Or short form:
_div [ Hx.get "/account"; Hx.pushUrlOn ] [
    _text "Go to My Account" ]

// Or specify URL:
_div [ Hx.get "/account"; Hx.pushUrl "/my-account" ] [
    _text "Go to My Account" ]

hx-sync

_form [ Hx.post "/store" ] [
    _input [ _name_ "title"; Hx.post "/validate"; Hx.trigger "change"; Hx.sync ("form", HxSync.Abort) ] ]

hx-include

_button [ Hx.post "/register"; Hx.includeCss "[name=email]" ] [
    _text "Register!" ]
_span [] [
    _text "Enter email: "
    _input [ _name_ "email"; _type_ "email" ] [] ]

// Hx.includeCss "[name=email]" is equivalent to:
_button [ Hx.post "/register"; Hx.include' (HxTarget.Css "[name=email]") ] [
    _text "Register!" ]
_span [] [
    _text "Enter email: "
    _input [ _name_ "email"; _type_ "email" ] [] ]

hx-params

_div [ Hx.get "/example"; Hx.params "*" ] [
    _text "Get Some HTML, Including Params" ]

hx-vals

_div [ Hx.get "/example"; Hx.vals """{"myVal": "My Value"}""" ] [
    _text "Get Some HTML, Including A Value in the Request" ]

// Or with a dynamic value:
_div [ Hx.get "/example"; Hx.vals "js:{myVal: calculateValue()}" ] [
    _text "Get Some HTML, Including a Dynamic Value from Javascript in the Request" ]

hx-confirm

_button [ Hx.delete "/account"; Hx.confirm "Are you sure you wish to delete your account?" ] [
    _text "Delete My Account" ]

hx-disable

_div [ Hx.disable ] []

hx-disabled-elt

_button [ Hx.post "/example"; Hx.disabledThis ] [
    _text "Post It!" ]

// Equivalent to:
_button [ Hx.post "/example"; Hx.disabled HxTarget.This ] [
    _text "Post It!" ]

hx-inherit

_div [ Hx.targerCss "#tab-container"; Hx.inherit' "hx-target" ] [
    _a [ Hx.boostOn; _href_ "/tab1" ] [ _text "Tab 1" ]
    _a [ Hx.boostOn; _href_ "/tab2" ] [ _text "Tab 2" ]
    _a [ Hx.boostOn; _href_ "/tab3" ] [ _text "Tab 3" ] ]

hx-disinherit

_div [ Hx.boostOn; Hx.select "#content"; Hx.targetCss "#content"; Hx.disinherit "hx-target" ] [
    _button [ Hx.get "/test" ] [] ]

hx-encoding

_form [ Hx.encodingMultipart ] [
    (* ... form controls ... *) ]

hx-ext

_div [ Hx.ext "example" ] [
    _text "Example extension is used in this part of the tree..."
    _div [ Hx.ext "ignore:example" ] [
        _text "... but it will not be used in this part." ] ]

hx-headers

_div [ Hx.get "/example"; Hx.headers [ "myHeader", "My Value" ] ] [
    _text "Get Some HTML, Including A Custom Header in the Request" ]

// Or to evaluate a dynamic value:
_div [ Hx.get "/example"; Hx.headers ([ "myHeader", "calculateValue()" ], true) ] [
    _text "Get Some HTML, Including A Custom Header in the Request" ]
// ^-- produces hx-headers='js:{"myHeader": calculateValue()}'

hx-history

_div [ Hx.historyOff ] []

hx-history-elt

_div [ Hx.historyElt ] []

hx-indicator

_div [] [
    _button [ Hx.post "/example"; Hx.indicator "#spinner" ] [
        _text "Post It!" ]
    _img [ _id_ "spinner"; _class_ "htmx-indicator"; _src_ "/img/bars.svg" ] ]

htmx Response Modifiers

HX-Location

Response.withHxLocation "/new-location"
>> Response.ofHtml (_h1' "HX-Location")

// this is equivalent to:
Response.withHxLocationOptions ("/new-location", None)
>> Response.ofHtml (_h1' "HX-Location")

// with context:
let ctx = HxLocation(event = "click", source = HxTarget.This)
Response.withHxLocationOptions ("/new-location", Some ctx)
>> Response.ofHtml (_h1' "HX-Location")

HX-Push-Url

Response.withHxPushUrl "/new-push-url"
>> Response.ofHtml (_h1' "HX-Push-Url")

HX-Redirect

Response.withHxRedirect "/redirect-url"
>> Response.ofHtml (_h1' "HX-Redirect")

HX-Refresh

Response.withHxRefresh
>> Response.ofHtml (_h1' "HX-Refresh")

HX-Replace-Url

Response.withHxReplaceUrl "/replace-url"
>> Response.ofHtml (_h1' "HX-Replace-Url")

HX-Reswap

Response.withHxReswap HxSwap.InnerHTML
>> Response.ofHtml (_h1' "HX-Reswap")

// with selector:
Response.withHxReswap (HxSwap.OuterHTML, "#falco")
>> Response.ofHtml (_h1' "HX-Reswap")

HX-Retarget

Response.withHxRetarget HxTarget.This
>> Response.ofHtml (_h1' "HX-Retarget")

// with selector:
Response.withHxRetarget (HxTarget.Css "#falco")
>> Response.ofHtml (_h1' "HX-Retarget")

HX-Trigger

Response.withHxTrigger (HxTriggerResponse.Events [ "myEvent" ])
>> Response.ofHtml (_h1' "HX-Trigger")

// or with detailed events (content is serialized to JSON):
Response.withHxTrigger (HxTriggerResponse.DetailedEvents [ ("myEvent", {| someData = 123 |}) ])
>> Response.ofHtml (_h1' "HX-Trigger")

HX-Trigger-After-Settle

Response.withHxTrigger (HxTriggerResponse.Events [ "myEvent" ])
>> Response.withHxTriggerAfterSettle
>> Response.ofHtml (_h1' "HX-Trigger-After-Settle")

HX-Trigger-After-Swap

Response.withHxTrigger (HxTriggerResponse.Events [ "myEvent" ])
>> Response.withHxTriggerAfterSwap
>> Response.ofHtml (_h1' "HX-Trigger-After-Swap")

HX-Reselect

Response.withHxReselect "#falco"
>> Response.ofHtml (_h1' "HX-Reselect")

Template Fragments

Falco.Htmx has built-in support for template fragments. This allows you to return only a fragment of a larger HTML document in response to an htmx request, without having to create separate template function for each fragment.

This is supported by the Response.ofFragment function, the hx-swap attribute and optionally the hx-select attribute.

For an overview, see the Click & Load example.

Kudos

Big thanks and kudos to @dpraimeyuu for their collaboration in starting this repo!

Find a bug?

There's an issue for that.

License

Licensed under Apache License 2.0.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
1.2.0 441 12/22/2025
1.1.0 429 9/15/2025
1.0.2 175 9/14/2025
1.0.1 336 6/5/2025
1.0.0 473 1/29/2025
1.0.0-rc2 154 1/25/2025
1.0.0-rc1 179 12/17/2024
1.0.0-beta2 151 11/20/2024
1.0.0-beta1 172 11/15/2024
0.0.5 283 3/19/2024
0.0.4 291 1/10/2024