JJConsulting.Html provides a fluent, low-allocation, strongly typed API for constructing HTML in .NET.
It is suitable for scenarios where a templating engine is not desirable, and where full programmatic control is required.
- Fluent API for building HTML trees
- Attribute helpers with conditional logic
- HTML encoding for text and attribute values
- Low allocations through
TextWriter - Common element helpers:
Div,Span,Input,Label,A,Br,Hrpowered by source generators
- Generating HTML fragments programmatically in backend code
- Creating ASP.NET Core Tag Helpers
- Creating reusable UI builders without Razor, like in JJMasterData.
- HTML emails or templated documents
- Automated content generators and utilities
- High-performance scenarios where templating engines are too heavy
dotnet add package JJConsulting.Html
var html =
new HtmlBuilder(HtmlTag.Div)
.WithCssClass("container")
.AppendDiv(d =>
d.WithCssClass("header")
.AppendText("Hello world!")
)
.AppendBr()
.AppendLink("Click here", "https://example.com")
.ToString(true);Produces:
<div class="container">
<div class="header">Hello world!</div>
<br />
<a href="https://example.com">Click here</a>
</div>new HtmlBuilder("Hello <b>world</b>"); // Encoded
new HtmlBuilder("Hello <b>world</b>", encode:false); // Not encodednew HtmlBuilder(HtmlTag.Input)
.WithName("email")
.WithId("email")
.WithValue("[email protected]")
.WithCssClass("form-control");var root = new HtmlBuilder(HtmlTag.Div);
root.Append(HtmlTag.Span, span => span.AppendText("Inside span"));
root.AppendText(" Just text ");
root.AppendBr();
root.AppendHiddenInput("token", "abc123");builder.AppendIf(isLogged, HtmlTag.Div, div => div.AppendText("Welcome"));
builder.WithAttributeIf(isAdmin, "data-role", "admin");
builder.AppendTextIf(showText, "Visible text");
builder.AppendScriptIf(debug, "console.log('debug');");builder.AppendScript("alert('Hello');");Produces:
<script type="text/javascript">alert('Hello');</script>AppendStyle.
var page =
new HtmlBuilder(HtmlTag.Div)
.WithCssClass("page")
.AppendDiv("Header", (text, div) =>
div.WithCssClass("header").AppendText(text)
)
.AppendDiv(div =>
{
div.WithCssClass("content");
div.AppendText("Some content here.");
div.AppendBr();
div.AppendLink("Read more", "/more");
})
.AppendScript("console.log('Page loaded');")
.ToString(true);In addition to the fluent HtmlBuilder API, the library also provides an optional declarative DSL based on static extension methods such as Div(...), Span(...), H1(...), P(...), etc.
These helpers let you compose HTML trees using nested expressions instead of chained fluent calls.
It's directly inspired by Giraffe.
using static JJConsulting.Html.Extensions.HtmlBuilderTagExtensions;
var html =
Div(
H1("Welcome"),
P("This is a compact DSL."),
Ul(
Li("One"),
Li("Two"),
Li("Three")
)
).ToString(true);Produces:
<div>
<h1>Welcome</h1>
<p>This is a compact DSL.</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>This style is ideal when:
- You prefer expression-based UI construction
- You want a clean declarative style similar to JSX, XML literals, or Razor Components
- You want minimal syntax noise while preserving strong typing
The DSL and the fluent API are fully interoperable:
var block =
Div(
H2("Title"),
Div().WithCssClass("box").AppendText("Inside")
);This allows you to use fluent attribute/child configuration while still benefiting from expressive tag factories.
Using the new C# 14 extension members, you can easily extend HtmlBuilder.
public static class HtmlBuilderExtensions
{
extension(HtmlBuilder html)
{
public HtmlBuilder WithData(string name, string value)
{
return html.WithAttribute($"data-bs-{name}", value);
}
public HtmlBuilder WithToolTip(string? tooltip)
{
if (string.IsNullOrEmpty(tooltip))
return html;
html.WithAttribute("title", tooltip);
html.WithData("toogle", "tooltip");
return html;
}
}
}