What is Kita Html

Estimated reading time: 4 minutes

Kita Html is a JSX runtime where every element evaluates to a string. Where React's <div /> produces a virtual DOM node that must be reconciled and serialized, Kita Html's <div /> returns '<div></div>' directly. There is no intermediate representation, no diffing step, and no serialization pass. The JSX you write compiles to function calls that concatenate strings.

This architecture makes Kita Html significantly faster than virtual DOM-based renderers for any scenario where the output is an HTML string: server-side rendering, static site generation, email templates, HTMX applications, or any HTTP handler that sends HTML over the wire.

Go to getting started

Performance through simplicity

A virtual DOM renderer performs three operations per render cycle: it constructs an object tree, diffs it against the previous tree, and serializes the result to a string. Kita Html skips all three. The TypeScript compiler rewrites JSX expressions into jsx() and jsxs() function calls at build time. At runtime, those functions concatenate attribute strings and child strings into a single result. The output is the final HTML with no post-processing.

// What you write
let html: JSX.Element
html
= (
<
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.class?: string | (string | number | boolean | null | undefined)[] | undefined

The html class property. You can use an array to represent conditional class names. Similar to the clsx package behavior.

@example
<div class={['a', true && 'b', false && 'c', 'd']} />
'<div class="a b d"></div>'

<div class={['class-a class-b', true && 'class-c']} />
'<div class="class-a class-b class-c"></div>'
class
="card">
<
JSX.IntrinsicElements.h1: JSX.HtmlTag
h1
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
const username: string
username
}</
JSX.IntrinsicElements.h1: JSX.HtmlTag
h1
>
</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
) // What TypeScript compiles to
let html: JSX.Element
html
=
function __jsx(this: void, name: string | Function, attributes: {
    children?: Children;
    [k: string]: any;
}): JSX.Element

Generates a html string from an attribute name of component and it's props.

This function is meant to be used by the jsx runtime and should not be called directly.

@paramname The name of the element to create or another component function@paramattributes The props to apply to the component@returnsThe generated html string or a promise that resolves to the generated html string
__jsx
('div', {
class: string
class
: 'card',
children?: Children
children
:
function __jsx(this: void, name: string | Function, attributes: {
    children?: Children;
    [k: string]: any;
}): JSX.Element

Generates a html string from an attribute name of component and it's props.

This function is meant to be used by the jsx runtime and should not be called directly.

@paramname The name of the element to create or another component function@paramattributes The props to apply to the component@returnsThe generated html string or a promise that resolves to the generated html string
__jsx
('h1', {
safe: boolean
safe
: true,
children?: Children
children
:
const username: string
username
}) }) // Click on the <> icon in the top right to see the compiled output
<div class="card"><h1>Username</h1></div>

This directness is why Kita Html benchmarks 7-40x faster than React's renderToString across real-world component trees. The runtime has zero dependencies beyond TypeScript itself.

Read more about how JSX becomes HTML

XSS protection

Because JSX.Element is a string, the runtime cannot distinguish between HTML produced by a component and raw user input injected as a child. Children are not escaped by default. This is a deliberate trade-off for performance and composability, and it is fully addressed by three layers of protection that work together to make accidental XSS practically impossible.

The safe attribute escapes children at render time. Adding it to any native element causes the runtime to pass all children through HTML entity escaping before concatenation.

const 
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
let userInput: "<script>alert('XSS')</script>"
userInput
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script>alert(&#39;XSS&#39;)&lt;/script></div>

A TypeScript language service plugin analyzes every JSX expression in your editor and flags any child whose type could carry unescaped HTML. The same analysis runs as a CLI tool xss-scan in CI/CD pipelines, failing the build on any finding. Between the editor diagnostics and the CI gate, unsafe expressions are caught before they reach production.

The only way to ship XSS-vulnerable code is to explicitly suppress the warnings.

Read more about the XSS protection system

Async components and Suspense

Kita Html supports async components natively. Any function component that returns a Promise<string> is valid JSX. When an async child appears anywhere in the tree, the entire parent chain becomes a Promise<string> through automatic propagation.

async function 
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
({
id: string
id
}: {
id: string
id
: string }) {
const
const user: {
    name: string;
}
user
= await
const db: {
    getUser(id: string): Promise<{
        name: string;
    }>;
}
db
.
function getUser(id: string): Promise<{
    name: string;
}>
getUser
(
id: string
id
)
return <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
const user: {
    name: string;
}
user
.
name: string
name
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
} // The result is a Promise<string> because UserCard is async, // Type still is JSX.Element so it can be used in JSX const
const html: JSX.Element
html
= <
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
id: string
id
="123" />
<div>Arthur</div>

For streaming scenarios, the Suspense component renders a fallback immediately and replaces it with the resolved content as each async subtree completes. This uses HTTP chunked transfer encoding to stream updates to the client without waiting for the entire page to resolve. A small inline script handles the client-side DOM replacement.

const 
const stream: Stream.Readable
stream
=
function renderToStream(html: JSX.Element | ((rid: number | string) => JSX.Element), rid?: number | string): Stream.Readable

Transforms a component tree who may contain Suspense components into a stream of HTML.

@example
import { text} from 'node:stream/consumers';

// Prints out the rendered stream (2nd example shows with a custom id)
const stream = renderToStream(r => <AppWithSuspense rid={r} />)
const stream = renderToStream(<AppWithSuspense rid={myCustomId} />, myCustomId)

// You can consume it as a stream
for await (const html of stream) {
 console.log(html.toString())
}

// Or join it all together (Wastes ALL Suspense benefits, but useful for testing)
console.log(await text(stream))
@paramhtml The component tree to render or a function that returns the component tree.@paramrid The request id to identify the request, if not provided, a new incrementing id will be used.@see{@linkcode Suspense}
renderToStream
((
rid: string | number
rid
) => (
<
function Suspense(props: SuspenseProps): JSX.Element

A component that returns a fallback while the async children are loading.

The rid prop is the one {@linkcode renderToStream } returns, this way the suspense knows which request it belongs to.

Warning: Using Suspense without any type of runtime support will LEAK memory and not work. Always use with renderToStream or within a framework that supports it.

Suspense
SuspenseProps.rid: string | number

The request id is used to identify the request for this suspense.

rid
={
rid: string | number
rid
}
SuspenseProps.fallback: JSX.Element

The fallback to render while the async children are loading.

fallback
={<
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>Loading...</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>}>
<
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
id: string
id
="123" />
</
function Suspense(props: SuspenseProps): JSX.Element

A component that returns a fallback while the async children are loading.

The rid prop is the one {@linkcode renderToStream } returns, this way the suspense knows which request it belongs to.

Warning: Using Suspense without any type of runtime support will LEAK memory and not work. Always use with renderToStream or within a framework that supports it.

Suspense
>
)) // Pipe the stream to your HTTP response or use one of our integrations.
<div id="B:1" data-sf><div>Loading...</div></div>
<!-- SuspenseScript omitted for brevity -->
<template id="N:1" data-sr><div>Async Component</div></template>
<script id="S:1" data-ss>
  $KITA_RC(1);
</script>

Each Suspense boundary operates independently, so fast components appear instantly while slow ones show their fallback. The rid parameter ties each Suspense instance to a specific request for safe concurrent rendering.

Packages

As of now, Kita Html is composed of two main packages:

  • @kitajs/html is the core runtime that compiles JSX to strings and provides Suspense streaming.
  • @kitajs/ts-html-plugin is the TypeScript plugin and CLI scanner for XSS detection.

Official framework integrations are available for Fastify, Elysia, and others. The core runtime works with any framework that accepts strings.