FluentHtml.Http 0.2.0

dotnet add package FluentHtml.Http --version 0.2.0
                    
NuGet\Install-Package FluentHtml.Http -Version 0.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="FluentHtml.Http" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FluentHtml.Http" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="FluentHtml.Http" />
                    
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 FluentHtml.Http --version 0.2.0
                    
#r "nuget: FluentHtml.Http, 0.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 FluentHtml.Http@0.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=FluentHtml.Http&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=FluentHtml.Http&version=0.2.0
                    
Install as a Cake Tool

FluentHtml — Server-Rendered HTML with a Fluent .NET API

CI NuGet Version .NET License Docs

Every ASP.NET team eventually faces the same choice: Razor mixes HTML and C# into a template language that resists refactoring. Blazor introduces a runtime, a component lifecycle, and hydration overhead that most line-of-business apps don't need. React/Vue/Angular require a separate language, a build pipeline, and a deployment story that doubles operational complexity. None of these are bad tools — they're just solving a bigger problem than most internal applications actually have.

FluentHtml takes a different approach. Instead of importing a foreign programming model, it makes HTML a native citizen of .NET — strongly typed, composable, IntelliSense-discoverable (in C#) — and keeps application logic on the server where ASP.NET Core excels. HTMX handles interactivity without a JavaScript framework. The result feels like writing idiomatic .NET, not porting another language's ideas.

Guiding principle: Never replace ASP.NET Core. Extend it with strongly typed HTML generation.

The Problem

// Razor — mixes template syntax with C#
<div class="card">
    <h2>@Model.Title</h2>
    @foreach (var item in Model.Items) {
        <p>@item.Name</p>
    }
</div>

// String concatenation — no IntelliSense, no refactoring, no safety
html += "<div class=\"card\">";
html += "<h2>" + title + "</h2>";

// Blazor — runtime overhead for simple server-rendered pages
<div class="card">
    <h2>@Title</h2>
    @foreach (var item in Items) {
        <p>@item.Name</p>
    }
</div>

Razor forces a template language that mixes two syntaxes in one file. String concatenation has no compile-time safety. Blazor adds a runtime, state management, and a rendering pipeline for pages that could be simple HTML. There is no lightweight, strongly typed, fluent way to generate HTML in .NET that integrates naturally with Minimal APIs and produces clean output.

FluentHtml eliminates the seam. It provides the strongly typed building blocks that make HTML generation a first-class .NET operation — not a template language, not a string, not a separate runtime.

How It Works

The entire component model lives in FluentHtmlpure .NET with no third-party dependencies. Targets net8.0 and net10.0. ASP.NET Core integration, HTMX support, and Bootstrap components are separate packages you opt into.

┌──────────────────────────────────────────────────────────────┐
│                        FluentHtml                            │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐  │
│  │     Node       │  │    Element     │  │   Component    │  │
│  │   .Children    │  │   .TagName     │  │   .Render()    │  │
│  │   .AddChild()  │  │   .Attributes  │  │   .Build()     │  │
│  │                │  │   .Classes     │  │                │  │
│  └────────────────┘  └────────────────┘  └────────────────┘  │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐  │
│  │   TextNode     │  │    RawHtml     │  │   Fragment     │  │
│  │   .Content     │  │   .Content     │  │   (no wrapper) │  │
│  │   (encoded)    │  │   (raw)        │  │                │  │
│  └────────────────┘  └────────────────┘  └────────────────┘  │
│  ┌────────────────────────────────────────────────────────┐  │
│  │        ~120 HTML Elements (Div, Span, Button, ...)     │  │
│  │     Fluent attributes: .Id() .Class() .HxPost()        │  │
│  └────────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  FluentHtml.Http — IResult, Minimal API helpers        │  │
│  │  FluentHtml.Htmx       — hx-get, hx-post, hx-swap      │  │
│  │  FluentHtml.Bootstrap  — Card, Alert, Button, Modal    │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

Every element is immutable after construction — fluent methods return the same instance. Thread-safe by design. No defensive copies needed.

Not a Clone — An Idiom

FluentHtml is inspired by FastHTML's philosophy of composing HTML from function calls, but it is designed as an idiomatic .NET framework that follows .NET conventions rather than porting another language's patterns:

  • Extension methods instead of static functions — Button().Primary() reads like fluent .NET, not F# pipe operators
  • Generics and type constraintsComponent<T> with compile-time safety, not duck typing
  • Immutable element trees — built via constructors, not mutable builder chains
  • ASP.NET Core nativeIResult integration, Minimal API extensions, not a standalone renderer
  • Strong typing everywhereButtonType.Submit instead of "submit", InputType.Email instead of "email"

The result is a framework that feels like writing .NET, not translating another language's idioms.

Fluent API — Not a Template Language

// FluentHtml — strongly typed, IntelliSense-discoverable
Button("Save")
    .Primary()
    .Large()
    .HxPost("/api/save")
    .Target("#result")

// Instead of Razor's mixed syntax:
// <button class="btn btn-primary btn-lg"
//         hx-post="/api/save"
//         hx-target="#result">Save</button>

Typing Button(). reveals Primary(), Secondary(), Disabled(), Id(), Class(), HxPost(), HxGet(), MarginTop(), Width() — developers should rarely need documentation for common tasks.

Composition, Not Inheritance

// Assemble building blocks — don't extend base classes:
var card = Card(
    H2("Customer Orders"),
    Table(
        Thead(new Tr(Th("Name"), Th("Amount"))),
        Tbody(orders.Select(o =>
            new Tr(new Td(o.Name), new Td(o.Amount.ToString("C")))
        ))
    ),
    Button("New Order")
        .Primary()
        .HxGet("/orders/new")
        .HxTarget("#dialog")
);

Components compose from elements. Elements compose from nodes. Pages compose from layouts. No inheritance hierarchies to navigate.

Server-Side Rendering — No Runtime Required

Browser → HTTP Request → ASP.NET Core Pipeline → Minimal API Route → FluentHtml Components → Renderer → HTML Response → Browser

FluentHtml owns only component creation and rendering. Everything before and after is standard ASP.NET Core — routing, DI, middleware, authentication, model binding, logging. HTMX requests behave exactly like normal requests; the only difference is partial HTML is returned instead of a complete page.

Use Cases

Internal Business Applications

app.MapGet("/customers", (CustomerService svc) =>
{
    return CustomersPage(service.GetAll());
});

Dashboards, admin panels, CRM, ERP, inventory systems — applications where the UI serves the data, not the other way around. Server-rendered HTML with HTMX partial updates provides the interactivity without the JavaScript overhead.

CRUD Systems

app.MapGet("/users/new", () => CreateUserForm());
app.MapPost("/users", (CreateUserDto dto) =>
{
    service.Create(dto);
    return UsersTable(service.GetAll()).ToHtmlResult();
});

Strongly typed forms with InputFor(x => x.Name), validation via ASP.NET Core's existing infrastructure, and HTMX partial rendering for seamless updates.

Dashboards and Reporting

app.MapGet("/dashboard", (DashboardService svc) =>
{
    return DashboardPage(
        MetricCards(svc.GetMetrics()),
        RecentOrders(svc.GetOrders(10)),
        SalesChart(svc.GetSalesData())
    );
});

Reusable components compose into complex layouts. Bootstrap integration provides the visual foundation.

Documentation and Portals

Clean, semantic HTML without the weight of a JavaScript framework. Server-side rendering provides excellent SEO and fast first-page load.

Technical Differentiators

vs. FluentHtml
Razor No template language — pure C# with compile-time safety, IntelliSense, and easy refactoring
Blazor No runtime, no hydration, no component lifecycle — just HTML generation and server responses
React/Vue/Angular No JavaScript build pipeline, no client-side state management, no deployment complexity
String concatenation Strongly typed elements, automatic HTML encoding, attribute management, composition
Tag Helpers Fluent API with method chaining — not attribute-based template extensions
FastHTML Idiomatic .NET — extension methods, generics, ASP.NET Core integration, not a language port

Packages

Package Description
FluentHtml.Core Core: Node, Element, Component<T>, Fragment, TextNode, RawHtml, Renderer, HtmlWriter, HtmlEncoder, ~120 HTML elements, fluent attributes, CSS helpers
FluentHtml.Http HtmlResult (IResult), Minimal API endpoint extensions, Node.ToHtmlResult()
FluentHtml.Htmx HxGet(), HxPost(), HxSwap(), HxTarget(), HxTrigger(), HxConfirm() and 20+ HTMX attribute extensions
FluentHtml.Bootstrap Card, Alert, Button, Navbar, Modal, Accordion, Toast, Dropdown, Pagination, Badge, Breadcrumb, Spinner + CSS helper extensions
FluentHtml.Forms Form, InputFor(), LabelFor(), SelectFor(), TextAreaFor(), CheckboxFor(), ValidationSummary()
FluentHtml.Validation ValidationMessage, ValidationSummary, validation CSS helpers

Installation

dotnet add package FluentHtml.Core
dotnet add package FluentHtml.Http
dotnet add package FluentHtml.Htmx
dotnet add package FluentHtml.Bootstrap
dotnet add package FluentHtml.Forms
dotnet add package FluentHtml.Validation

Quick Start

using FluentHtml.Elements;
using FluentHtml.Http;
using FluentHtml.Bootstrap.Components;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// A complete page — all .NET, no Razor, no templates
app.MapGet("/", () =>
{
    return new HtmlElement(
        new HeadElement(new TitleElement("FluentHtml Sample")),
        new BodyElement(
            new NavElement().Class("navbar navbar-dark bg-dark").Children(
                new AnchorElement("FluentHtml").Href("/").Class("navbar-brand")
            ),
            new MainElement().Class("container mt-4").Children(
                Card(
                    new Heading1Element("Welcome"),
                    new ParagraphElement("Built with strongly typed .NET components."),
                    Button("Click Me")
                        .Primary()
                        .Large()
                        .HxPost("/api/click")
                        .HxTarget("#result")
                ),
                new DivElement().Id("result")
            )
        )
    );
});

// Partial rendering for HTMX
app.MapGet("/api/click", () =>
{
    return AlertExtensions.Alert("Button clicked!")
        .Success()
        .ToHtmlResult();
});

app.Run();

Component Model

// Elements — one HTML tag each
var div = new DivElement(
    new Heading1Element("Dashboard"),
    new ParagraphElement("Welcome back.")
).Id("main").Class("container");

// Text nodes — auto-encoded
var p = new ParagraphElement("Hello <script>alert('xss')</script>");
// Renders: <p>Hello &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</p>

// Fragments — siblings without a wrapper
var fragment = new Fragment(
    new Heading1Element("Title"),
    new ParagraphElement("Body")
);

// Raw HTML — trusted, not encoded (use sparingly)
var raw = new RawHtml("<strong>Bold text</strong>");

Fluent Attributes

Button("Save")
    .Primary()              // class="btn btn-primary"
    .Large()                // class="btn btn-primary btn-lg"
    .Disabled()             // disabled
    .Id("save-btn")         // id="save-btn"
    .HxPost("/api/save")    // hx-post="/api/save"
    .HxTarget("#result")    // hx-target="#result"
    .HxConfirm("Save?")     // hx-confirm="Save?"

HTMX Integration

using FluentHtml.Htmx;

Button("Load Data")
    .HxGet("/api/data")
    .HxTarget("#table")
    .HxSwap("innerHTML")
    .HxTrigger("click")
    .HxIndicator("#spinner");

Button("Delete")
    .Danger()
    .HxDelete("/api/items/1")
    .HxConfirm("Are you sure?")
    .HxTarget("closest tr")
    .HxSwap("outerHTML");

Forms with Model Binding

using FluentHtml.Forms;

var form = new FormElement(
    new Heading2Element("Create User"),
    new DivElement(
        new LabelElement("Name").For("name"),
        new InputElement().Type("text").Name("name").Id("name").Required()
    ).Class("mb-3"),
    new DivElement(
        new LabelElement("Email").For("email"),
        new InputElement().Type("email").Name("email").Id("email").Required()
    ).Class("mb-3"),
    Button("Create").Primary()
).Action("/users").Method("post");

Testing

using FluentHtml.Elements;
using FluentHtml.Rendering;

var renderer = new Renderer();

var card = new DivElement(
    new Heading2Element("Test"),
    new ParagraphElement("Content")
).Class("card");

var html = renderer.Render(card);
Assert.Equal("<div class=\"card\"><h2>Test</h2><p>Content</p></div>", html);

No web server required. Components are plain .NET objects — instantiate, render, assert.

Node Hierarchy

Node
├── Element      — single HTML tag (Div, Span, Button, Form, Table, Input...)
├── TextNode     — plain text, always HTML encoded
├── RawHtml      — trusted HTML, not encoded (use sparingly)
├── Fragment     — groups siblings without a wrapper element
└── Component    — reusable UI, composes existing nodes

Every renderable object derives from Node. Elements own a tag name, attributes, and children. Components render other nodes. Fragments produce no wrapper HTML.

License

Apache License 2.0 — see LICENSE for details.

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 was computed.  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
0.2.0 37 7/30/2026
0.1.0 46 7/29/2026

Provides FluentRouter (MapGet/MapPost/MapPut/MapPatch/MapDelete), FormAntiforgery, HtmlResult, and NodeResultExtensions for seamless HTTP integration.