How JSX Becomes HTML

Estimated reading time: 2 minutes

The transformation from JSX to an HTML string happens in two stages: a compile-time rewrite by TypeScript and a runtime evaluation by the Kita Html functions.

Compile time

When tsconfig.json specifies jsx: "react-jsx" and jsxImportSource: "@kitajs/html", TypeScript rewrites every JSX expression into a function call. Elements with a single child call jsx(). Elements with multiple children call jsxs(). Attributes and children are passed as a single props object.

Source
const 
const html: JSX.Element
html
= (
<
JSX.IntrinsicElements.ol: JSX.HtmlOListTag
ol
JSX.HtmlOListTag.start?: string | number | undefined
start
={2}>
{[1, 2].
Array<number>.map<JSX.Element>(callbackfn: (value: number, index: number, array: number[]) => JSX.Element, thisArg?: any): JSX.Element[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((
i: number
i
) => (
<
JSX.IntrinsicElements.li: JSX.HtmlLITag
li
>{
i: number
i
}</
JSX.IntrinsicElements.li: JSX.HtmlLITag
li
>
))} </
JSX.IntrinsicElements.ol: JSX.HtmlOListTag
ol
>
)
<ol start="2">
  <li>1</li>
  <li>2</li>
</ol>

TypeScript compiles this to:

Compiled
import { jsx as _jsx } from "@kitajs/html/jsx-runtime";
const html = (_jsx("ol", { start: 2, children: [1, 2].map((i) => (_jsx("li", { children: i }))) }));
<ol start="2">
  <li>1</li>
  <li>2</li>
</ol>

The JSX syntax is fully removed at compile time. The runtime never parses JSX.

Runtime

The jsx() and jsxs() functions receive the tag name and the props object. They perform three operations in sequence: serialize attributes into an HTML attribute string, serialize children into a content string, and concatenate the opening tag, content, and closing tag.

If any child is a Promise, the concatenation returns a Promise<string> instead of a string. This propagation is automatic: a single async component anywhere in the tree makes every ancestor async up to the root.

Void elements like <br />, <img />, and <meta /> skip the closing tag entirely. The runtime checks the tag name against a list ordered by frequency so the most common void elements resolve in a single comparison.

Attributes are always escaped. The class attribute accepts arrays for conditional composition. The style attribute accepts both strings and objects, with automatic camelCase-to-kebab-case conversion for object keys. Boolean attributes like disabled render as valueless attributes when true and are omitted when false.