A minimal, zero-dependency library for building fully-styled HTML in TypeScript/JavaScript.
import { HTMLDocument, NodeElement, NodeText } from "htmlforge"
const html = new HTMLDocument()
html.attributeAdd("lang", "en-GB")
html.head.childAdd(
new NodeElement("title").childAdd(
new NodeText("Acme Title")
)
)
html.body.childAdd(
new NodeElement("div")
.styleAdd("width", "100%")
.styleAdd("background-color", "blue")
.styleAdd("background-color", "red", { pseudoSelector: ":hover" })
.childAdd(new NodeText("Hello world"))
)
const validHTML = html.toString()
Install the package from npm:
npm install htmlforge
An HTMLForge HTMLDocument instance is a tree of nodes. Nodes come in a few flavors:
NodeElement: represents tags (e.g., <div>, <span>), can hold attributes and inline styles, and can nest any other node as a child.NodeText: holds plain text content that will be HTML-escaped.NodeRaw: holds raw HTML without escaping.NodeFragment: groups a collection of child nodes without introducing a wrapping element.Create a new HTML document using new HTMLDocument(), set attributes on the root <html> element via html.attributeAdd, and work directly with html.head and html.body to populate content. The constructor accepts optional parameters:
indentCount (default 4): number of spaces used for pretty-print indentation in toString().signatureDisplay (default true): whether to include the <!-- Generated by HTMLForge --> signature comment.import { HTMLDocument, NodeElement, NodeText } from "htmlforge"
const html = new HTMLDocument({ indentCount: 2, signatureDisplay: false })
.attributeAdd("lang", "en")
.attributeAdd("data-theme", "dark")
html.body
.styleAdd("margin", "auto")
.childAdd(new NodeText("Hello world"))
NodeElement nodesNodeElement supports:
attributeAdd(name, value) for HTML attributesstyleAdd(property, value, options?) for inline styles (with optional pseudoSelector, mediaQuery parameters)childAdd(node) to nest children nodesThese calls are chainable to keep element construction compact.
import { NodeElement, NodeText } from "htmlforge"
const card = new NodeElement("section")
.attributeAdd("aria-label", "profile card")
.styleAdd("border", "1px solid #ccc")
.childAdd(
new NodeElement("h2").childAdd(new NodeText("Ada Lovelace"))
)
.childAdd(
new NodeElement("p")
.styleAdd("color", "#555")
.childAdd(new NodeText("First computer programmer."))
)
NodeFragment nodesNodeFragment groups child nodes without adding a wrapper element. It only supports childAdd (also chainable).
import { NodeFragment, NodeElement, NodeText } from "htmlforge"
const listItems = new NodeFragment()
.childAdd(new NodeElement("li").childAdd(new NodeText("One")))
.childAdd(new NodeElement("li").childAdd(new NodeText("Two")))
.childAdd(new NodeElement("li").childAdd(new NodeText("Three")))
NodeText holds HTML-escaped text content (no additional methods).NodeRaw injects raw HTML as-is (no additional methods).import { NodeText, NodeRaw } from "htmlforge"
const safeText = new NodeText("<em>Escaped</em> output")
const rawHtml = new NodeRaw("<em>Unescaped</em> output")
Implement the INode interface to build reusable components. Compose a private NodeElement (style/shape it however you like) and proxy its build() method. Anything that implements INode can be passed to childAdd on NodeElement or NodeFragment.
import type { INode } from "htmlforge"
import { NodeElement, NodeText } from "htmlforge"
class Alert implements INode {
private readonly el = new NodeElement("div")
.attributeAdd("role", "alert")
.styleAdd("padding", "12px 16px")
.styleAdd("background-color", "#fffae6")
constructor(message: string) {
this.el.childAdd(new NodeText(message))
}
// Optional: expose childAdd to let callers inject arbitrary child nodes
childAdd(child: INode) {
this.el.childAdd(child)
return this
}
build() {
return this.el.build()
}
}