Skip to main content
2024-present TypeScript Tailwind CSS HTMX Fastify

fluent-html

Zero-dependency, type-safe HTML builder for TypeScript with compile-time Tailwind CSS and HTMX safety

fluent-html screenshot

Overview

fluent-html is a zero-dependency, type-safe HTML builder library for TypeScript (7,531 lines of source, 6,733 lines of tests, ~247 commits over 2 years). It provides full compile-time safety for Tailwind CSS classes, HTMX attributes, route endpoints, element IDs, and form fields -- eliminating entire categories of runtime bugs while providing IDE autocomplete that makes documentation unnecessary. The library powers JT Digital's fullstack output with Fastify v5 + HTMX + Tailwind.

Architecture

fluent-html architecture

The codebase is split into 6 sub-packages with separate entry points enabling tree-shaking: core (Tag class, types, guards, mixins), elements (55+ HTML factories), control (IfThen, Match, ForEach, context), render (sync + stream), fold (recursion schemes), and routes/ids/htmx. Tag's functionality is distributed via TypeScript declaration merging + prototype assignment -- a mixin architecture that keeps the class extensible without monolithic inheritance.

Key Concepts

Variant Prefix State Machine

Variant Prefix State Machine

The .on() and .at() methods implement a stack-based variant prefix system. Each call saves the current prefix, sets a new compound prefix, calls the callback, then restores. Nested variants compose correctly: .on('hover', t => t.on('focus', t => t.ring('2'))) produces hover:focus:ring-2.

Type-Level Route Parameter Extraction

Type-Level Route Parameter Extraction

defineRoutes performs compile-time extraction of :param segments from path strings using recursive template literal types. Combined with HasParams and ResolveParamTypes, routes without params get a simple signature while routes with params require a typed params object -- all enforced at the type level with zero runtime cost.

Code Highlights

The View ADT -- entire type in one line
export type Thunk<T> = () => T;
export type View = Tag | string | RawString | View[];
Hylomorphism -- fused unfold-then-fold
export function hyloView<S, A>(
  coalg: ViewCoalgebra<S>, alg: ViewAlgebra<A>, seed: S
): A {
  const layer = coalg(seed);
  switch (layer.type) {
    case "text":
      return alg.text(layer.value);
    case "raw":
      return alg.raw(layer.html);
    case "tag": {
      const children = alg.list(
        layer.children.map(s => hyloView(coalg, alg, s))
      );
      return alg.tag(layer.element, layer.attrs, children);
    }
    case "list":
      return alg.list(layer.items.map(s => hyloView(coalg, alg, s)));
  }
}
Type-level route param extraction
type ExtractParams<Path extends string> =
  Path extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<`/${Rest}`>
    : Path extends `${string}:${infer Param}`
      ? Param
      : never;

type ResolveParamTypes<
  Path extends string,
  Params extends Readonly<Record<string, ParamTypeName>> | undefined,
> = {
  [K in ExtractParams<Path>]: Params extends Record<string, ParamTypeName>
    ? K extends keyof Params
      ? ParamTypeMap[Params[K]] : string
    : string;
};

Highlights

  • Designed a mixin architecture using TypeScript declaration merging + prototype assignment that splits a 1,100-line Tag class into focused modules with zero API changes
  • Implemented four recursion schemes (catamorphism, paramorphism, anamorphism, hylomorphism) on HTML trees with fusion law tests
  • Built a compile-time route safety system using recursive template literal types that extracts :param segments and resolves TypeScript types with zero runtime overhead
  • Engineered a multi-layer XSS protection system backed by property-based fuzz testing (1,000 random inputs per property)
  • Created a complete developer toolchain: 19-rule ESLint plugin, Tailwind CSS content extractor, and benchmark suite