Primitive Content
TextContent
Simple text or number values that will be escaped in HTML:
1type TextContent = string | number;
2
3// Examples
4("Hello world"); // -> "Hello world"
542; // -> "42"
HtmlContent
Raw HTML content that will be inserted as-is:
1interface HtmlContent {
2 ​type: "html";
3 ​content: string;
4 ​meta: Meta;
5}
6
7// Example
8html("<div>Raw HTML</div>");
Nodes
Fragment
Container for multiple nodes without creating an element:
1interface FragmentNode extends BaseNode {
2 ​type: "fragment";
3 ​children: Node[];
4 ​meta: Meta;
5}
6
7// Example
8<>
9 ​<div>First</div>
10 ​<div>Second</div>
11</>;
Element
Basic HTML element node:
1interface ElementNode extends BaseNode {
2 ​type: "element";
3 ​tag: string;
4 ​attributes: ElementAttributes;
5 ​children: Node[];
6 ​meta: Meta;
7}
Attributes Normalization
The system normalizes attributes for consistent processing:
Class attributes:
1// Input variants2{ class: "foo bar" }3{ class: ["foo", "bar"] }4{ class: { foo: true, bar: false } }5{ className: "foo" }67// Normalized output8{ class: ["foo", "bar"] }Style attributes:
1// Input variants2{ style: "color: red; margin-top: 10px" }3{ style: { color: "red", marginTop: "10px" } }4{ style: ["color: red", { marginTop: "10px" }] }56// Normalized output7{ style: {8 ​"color": "red",9 ​"margin-top": "10px"10}}Other attributes:
CamelCase converted to kebab-case
CSS variables (--custom-prop) preserved
data-* attributes preserved
Boolean attributes simplified
null/undefined values removed
Component
Custom component node with its own render logic:
1interface ComponentNode<T extends Record<string, any>>
2 ​extends BaseNode<{
3 ​ ​component: {
4 ​ ​ ​id: string;
5 ​ ​ ​render: (attrs: T, children?: Node[]) => Node;
6 ​ ​};
7 ​}> {
8 ​type: "component";
9 ​attributes: T;
10 ​children: Node[];
11 ​meta: Meta;
12}
13
14// Example
15const Button = component<ButtonProps>((props) => (
16 ​<button class={["btn", `btn-${props.variant}`]}>{props.children}</button>
17));