This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
CC.CSX is a C# HTML generation library that allows developers to create HTML declaratively using pure C# code (JSX-like / Hiccup-like). Six NuGet packages live in this repo:
- CC.CSX (
src/CC.CSX/): Core library — HTML element/attribute factories and rendering - CC.CSX.Web (
src/CC.CSX.Web/): ASP.NET Core integration —HtmlResult(implementsIResult,IActionResult,IEndpointMetadataProvider) - CC.CSX.Htmx (
src/CC.CSX.Htmx/): HTMX attribute extensions (hxPost,hxGet,target, …) andHtmxImports. Depends only on CC.CSX (not CC.CSX.Web), so it loads in browser-wasm too - CC.CSX.Css (
src/CC.CSX.Css/): Strongly typed CSS sidecar —CssClass(incl. virtual/higher-order composed classes),CssDeclaration+ lowercaseCssPropertiesfactories,CssUnits(8.px()),CssBundle/CssImports. Ships thesrc/CC.CSX.Css.Generator/source generator (netstandard2.0) which turns.cssfiles registered asAdditionalFilesinto typed members:Css.<FileName>.<className>constants, a baked-inSourceconst and aBundle. Convention: elements are PascalCase, styles/classes are lowercase. Plain strings keep working everywhere (CssClass⇄stringimplicit conversions). Generation chain (regenerate, don't hand-edit):eng/gen-css-property-keys.py(MDN data →CssPropertyKeys.cs, keeps legacy/at-rule entries) theneng/gen-css-properties.py(CssPropertyKeys.cs→CssProperties.cs, skips at-rules). Seesamples/Webfor end-to-end usage andtests/CC.CSX.Css.Testsfor behavior. - CC.CSX.Browser (
src/CC.CSX.Browser/): Client-side rendering in .NET WASM (no Blazor) —BrowserApp.MountAsync("#app", View)renders the view and morphs the DOM in place (focus/caret survive; morph + delegated capture-phase listeners live inccsx-browser.js, shipped as an embedded resource imported via adata:URL ES module, so host apps need no asset wiring). Events:using static CC.CSX.Browser.HtmlEventsgives delegate overloadsonClick(Action)/onClick(Action<DomEvent>)/async variants for all 33 events (HtmlEvents.g.cs, generated byeng/gen-browser-events.pyfromHtmlAttributeKeys_Events.cs— regenerate, don't hand-edit), genericon("pointerdown", …), plus htmx-style stable named actions:htAction("counter/reset")+BrowserApp.Map("counter/reset", handler). Handlers render asdata-ht-on-<event>attributes — numeric ids are per-render (delegates), strings are stable action names. Also intercepts CC.CSX.Htmxhx-get/post/put/patch/deletemarkup client-side:BrowserApp.MapGet/MapPost/…("/todos/{id}", req => …)routes (Minimal-API-style templates,HxRequestcarries merged query/form/path params), with hx-target (this/closest/find/selector) and hx-swap (innerHTML/outerHTML/before*/after*/delete/none/morph) subsets — same markup works server-side with htmx.js or in-browser with no server.BrowserApp.StartAsync(selector)runs hx routing over static DOM without mounting a view. Caveat: delegate ids inside hx-swapped fragments die on the next fullRefresh— prefer hx/htActionattributes inside route responses. Seesamples/BrowserSample(run withmake run.browser) andtests/CC.CSX.Browser.Tests. - CC.CSX.Css.Tailwind (
src/CC.CSX.Css.Tailwind/): Strongly typed Tailwind utilities —Twstatic class with ~2900 default-themeCssClassconstants (Tw.Classes.g.cs, generated byeng/gen-tailwind-classes.py— regenerate, don't hand-edit), variant combinator methods (hover,focus,dark,sm–xl2, genericvariant),neg/important, and arbitrary-value methods (mt("17px")→mt-[17px]). Caution:using static Twalongsideusing static CssPropertiescollides on names likegap/flex— use qualifiedTw.*in files that import both.
# Build the solution (SDK pinned to 10.0.100 in global.json)
dotnet build
# Run all tests with coverage
make test
# or plain: dotnet test
# Run a single test project / single test
dotnet test tests/CC.CSX.Tests
dotnet test --filter "FullyQualifiedName~GeneralRenderingTests.MyTestName"
# Benchmarks (BenchmarkDotNet)
dotnet run -c Release --project tests/CC.CSX.Benchmarks
# Run samples
make run.simple # Console sample, prints HTML
make run.htmx # HTMX web sample
make run.web # ASP.NET Core web sample
make run.browser # .NET WASM client-side sample (CC.CSX.Browser)
make watch.htmx / make watch.web # Watch modemake package.all # Pack all three packages (eng/package-*.sh)
make publish.all # Version bump (VERSION file via eng/version-bump.sh) + pack + publishVersioning is driven by the root VERSION file (MAJOR/MINOR/PATCH); eng/ holds the packaging/publishing scripts.
Everything derives from the abstract HtmlItem (Name + optional Value, abstract ToString(indent) / AppendTo(ref StringBuilder) / WriteTo(ref TextWriter)):
- HtmlNode: an element with
Attributes(List<HtmlAttribute>) andChildren(List<HtmlNode>). Constructors takeparams HtmlItem[]— attributes and children are mixed in one list and sorted out at construction. - HtmlTextNode: text content (with HTML escaping)
- Fragment: renders only its children, no wrapping tag (
Fragment.Emptysingleton) - HtmlNone: renders nothing
- HtmlAttribute and subclasses:
HtmlClassAttribute(joins classes with spaces),HtmlStyleAttribute(joins(key, value)tuples with;),HtmlEventAttribute,MultiHtmlAttribute(an attribute holding a list of attributes — whatHtmlAttribute[]converts to)
Note: Domain/Component.cs and HtmlElementExtensions.cs are fully commented out — there is no Component base class despite the file existing.
These make the declarative syntax work; check them before changing signatures:
string,int,bool,float,double→HtmlTextNode(string key, string? value)tuple →HtmlAttributeHtmlNode[]/List<HtmlNode>→FragmentHtmlAttribute[]→MultiHtmlAttribute
- HtmlElements / HtmlAttributes: one static method per HTML element/attribute, intended for
using staticimport. Attribute methods are lowercase (id(...),@class(...),style(...)). - HtmlElementKeys, HtmlAttributeKeys, CssPropertyKeys: large generated-style classes of string constants for element names, attribute names, and CSS property names. CSS support (the
feature/css-supportbranch) builds onCssPropertyKeys+HtmlStyleAttribute.
- Three parallel render paths on every
HtmlItem:ToString(indent),AppendTo(ref StringBuilder), andWriteTo(ref TextWriter)(the streaming path used by the web integration). New node types must implement all three. - RenderOptions is global static state (
Indent,TextNodeOnNewLine) that affects formatting everywhere — including test expectations. CC.CSX.Web.HtmlResultstreams viaWriteToto the responseBodyWriterwithContent-Type: text/html;Render(...)extension methods are inCC.CSX.Web/HtmlNodeExtensions.cs.
using static CC.CSX.HtmlElements;
using static CC.CSX.HtmlAttributes;
using static CC.CSX.Htmx.HtmxAttributes; // if using HTMX
Div(@class("container"), id("main"),
H1("Title"),
P("Content here"),
("data-custom", "value") // tuple → attribute
)- CC.CSX: net10.0, net10.0-android; CC.CSX.Web / CC.CSX.Htmx: net10.0 only
LangVersion=previewwith preview features enabled across projects
tests/: xUnit tests (CC.CSX.Tests,CC.CSX.Htmx.Tests) +CC.CSX.Benchmarkssamples/: Simple (console), HtmxSample, Web, CalendarSampledocs/: DocFX documentation site