---
title: "JSX Runtime"
description: "Hono's JSX runtime provides both server-side rendering and client-side DOM execution models JSX Components and Context, Client DOM and Hooks. It processes functional components and intrinsic tags t..."
last_updated: "2026-10-12T08:14:12.740617+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-runtime"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/jsx/jsx-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts)
- [src/jsx/base.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts)
- [src/jsx/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/components.ts)
- [src/jsx/dom/render.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/render.ts)
- [src/jsx/streaming.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/streaming.ts)
- [src/jsx/dom/intrinsic-element/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/intrinsic-element/components.ts)
- [src/jsx/intrinsic-element/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/intrinsic-element/components.ts)
- [src/jsx/jsx-dev-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts)
- [src/jsx/hooks/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/hooks/index.ts)
- [src/jsx/dom/jsx-dev-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts)
- [src/middleware/jsx-renderer/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jsx-renderer/index.ts)
- [src/jsx/dom/server.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts)
- [src/jsx/dom/jsx-runtime.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-runtime.ts)
- [src/jsx/dom/client.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/client.ts)
- [src/jsx/dom/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/index.ts)
- [src/helper/html/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/html/index.ts)
- [src/jsx/dom/components.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/components.ts)
- [src/utils/html.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/html.ts)
- [src/jsx/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/index.ts)
- [src/jsx/dom/css.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/css.ts)
- [src/jsx/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/context.ts)
- [src/helper/css/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/css/index.ts)
- [runtime-tests/deno-jsx/deno.react-jsx.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno-jsx/deno.react-jsx.json)
- [runtime-tests/deno-jsx/deno.precompile.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno-jsx/deno.precompile.json)
- [src/jsx/children.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts)
- [src/jsx/dom/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/context.ts)
- [runtime-tests/deno/deno.json](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno/deno.json)
- [src/jsx/intrinsic-elements.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/intrinsic-elements.ts)
- [src/jsx/types.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/types.ts)
- [src/jsx/constants.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/constants.ts)
</details>

## Overview

Hono's JSX runtime provides both server-side rendering and client-side DOM execution models [JSX Components and Context](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-components-and-context), [Client DOM and Hooks](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/client-dom-and-hooks). It processes functional components and intrinsic tags through unified entry points [src/jsx/jsx-runtime.ts:6-8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L8), [src/jsx/jsx-dev-runtime.ts:12-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts#L12-L25), validates and escapes attributes [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49), and serializes hierarchical nodes into string buffers or browser DOM trees [src/jsx/base.ts:249-302](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L249-L302), [src/jsx/dom/render.ts:41-80](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/render.ts#L41-L80).

Sources: [src/jsx/jsx-runtime.ts:6-8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L8), [src/jsx/jsx-dev-runtime.ts:12-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts#L12-L25), [src/jsx/base.ts:249-302](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L249-L302), [src/jsx/dom/render.ts:41-80](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/render.ts#L41-L80), [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49)

## JSX Runtime Entry Points

### Automatic and Classic Runtimes

Hono supports both automatic (`react-jsx`) and development JSX transforms through dedicated module entry points [runtime-tests/deno-jsx/deno.react-jsx.json:3-4](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno-jsx/deno.react-jsx.json#L3-L4), [src/jsx/jsx-runtime.ts:6-8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L8). The primary production runtime module `hono/jsx/jsx-runtime` exports `jsx`, `jsxs`, and `Fragment` directly from `jsx-dev-runtime`, alongside `jsxTemplate` (aliased from `html`) and utility helpers such as `jsxAttr` and `jsxEscape` [[src/jsx/jsx-runtime.ts:6-14], [src/jsx/jsx-runtime.ts:51]].

```typescript
export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'
export { jsxDEV as jsxs } from './jsx-dev-runtime'
export type { JSX } from './jsx-dev-runtime'
import { html, raw } from '../helper/html'
```
Sources: [src/jsx/jsx-runtime.ts:6-9](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L9)

The development runtime function `jsxDEV` accepts a tag, a props dictionary, and an optional key, normalizing single child properties and arrays into structured node hierarchies via `jsxFn` [src/jsx/jsx-dev-runtime.ts:12-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts#L12-L25).

```typescript
export function jsxDEV(
  tag: string | Function,
  props: Record<string, unknown>,
  key?: string
): JSXNode {
  let node: JSXNode
  if (!props || !('children' in props)) {
    node = jsxFn(tag, props, [])
  } else {
    const children = props.children as string | HtmlEscapedString
    node = Array.isArray(children) ? jsxFn(tag, props, children) : jsxFn(tag, props, [children])
  }
  node.key = key
  return node
}
```
Sources: [src/jsx/jsx-dev-runtime.ts:12-25](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-dev-runtime.ts#L12-L25)

> [!NOTE]
> When configured with Deno and TypeScript compiler options like `"jsx": "react-jsx"` and `"jsxImportSource": "hono/jsx"`, compiler targets resolve module specifiers against `hono/jsx/jsx-runtime` [[runtime-tests/deno-jsx/deno.react-jsx.json:3-4], [runtime-tests/deno-jsx/deno.react-jsx.json:10]].

Sources: [runtime-tests/deno-jsx/deno.react-jsx.json:3-4](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno-jsx/deno.react-jsx.json#L3-L4), [runtime-tests/deno-jsx/deno.react-jsx.json:10](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/runtime-tests/deno-jsx/deno.react-jsx.json#L10)

### Attribute Processing and Exports

The `jsxAttr` function evaluates attribute keys and dynamic values, checking attribute name validity and formatting styles, numbers, promises, or raw escaped strings into valid markup attributes [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49).

| Parameter / Export | Type / Behavior | Source Reference |
| :--- | :--- | :--- |
| `jsx` / `jsxs` | Aliased to `jsxDEV` for automatic JSX compilation | [src/jsx/jsx-runtime.ts:6-7](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L7) |
| `Fragment` | Component grouping container exported from dev runtime | [src/jsx/jsx-runtime.ts:6](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6) |
| `jsxTemplate` | Aliased to `html` helper for template literals | [src/jsx/jsx-runtime.ts:14](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L14) |
| `jsxAttr` | Function validating attribute keys and formatting values | [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49) |
| `jsxEscape` | Identity helper function returning raw string values | [src/jsx/jsx-runtime.ts:51](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L51) |

Sources: [src/jsx/jsx-runtime.ts:6-14](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L6-L14), [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49), [src/jsx/jsx-runtime.ts:51](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L51)

## JSX Node Hierarchy

### Node Factories and Creation

The `jsx` factory function accepts a tag, a props dictionary, and variadic children, handling prop initialization and key extraction before delegating to `jsxFn` [src/jsx/base.ts:305-317](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L305-L317). The `jsxFn` function evaluates the tag type to instantiate the appropriate hierarchy node [src/jsx/base.ts:320-352](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L320-L352).

```typescript
export const jsx = (tag: string | Function, props: Props | null, ...children: Child[]): JSXNode => {
  props ??= {}
  if (children.length) {
    props.children = children.length === 1 ? children[0] : children
  }

  const key = props.key
  delete props['key']

  const node = jsxFn(tag, props, children)
  node.key = key
  return node
}
```
Sources: [src/jsx/base.ts:305-317](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L305-L317)

When `tag` is a function, `jsxFn` returns a `JSXFunctionNode` [src/jsx/base.ts:330-331](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L330-L331). Intrinsic element tags from lookup dictionaries instantiate `JSXFunctionNode` mapped to their intrinsic renderer [src/jsx/base.ts:332-337](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L332-L337). Namespace-sensitive tags such as `svg` and `head` wrap children in a namespace context component node inside a base `JSXNode` [src/jsx/base.ts:338-349](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L338-L349), while remaining tags construct a standard `JSXNode` [src/jsx/base.ts:350-352](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L350-L352).

> [!NOTE]
> On the initial call to `jsxFn`, `domRenderers` bindings are assigned to intrinsic element tags using the `DOM_RENDERER` symbol property, initializing the client DOM rendering pipeline [src/jsx/base.ts:319-328](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L319-L328).

Sources: [src/jsx/base.ts:319-328](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L319-L328)

### Node Types and Specialization

Specialized node classes extend the hierarchy to handle functional evaluation and document fragments [src/jsx/base.ts:255-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L303). The `JSXFunctionNode` executes functional components by invoking the component function with its properties and children, processing return values like promises, nested nodes, or escaped strings [src/jsx/base.ts:255-296](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L296). The `JSXFragmentNode` implements container nodes that serialize their children directly to buffers without rendering surrounding tags [src/jsx/base.ts:299-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L299-L303).

```typescript
class JSXFunctionNode extends JSXNode {
  override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
    const { children } = this

    const props = { ...this.props }
    if (children.length) {
      props.children = children.length === 1 ? children[0] : children
    }

    const res = (this.tag as Function).call(null, props)
    // ...
  }
}
```
Sources: [src/jsx/base.ts:255-265](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L265)

React-compatible element definitions and validation helpers support element checking and tree cloning [[src/jsx/types.ts:19-23], [src/jsx/base.ts:422-443]]. The `isValidElement` function validates unknown inputs [src/jsx/base.ts:422-424](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L422-L424), and `cloneElement` merges new properties and children into existing element nodes [src/jsx/base.ts:426-443](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L426-L443). Memoized functional components (`memo`) wrap components with `shallowEqual` checks to skip redundant computations [src/jsx/base.ts:354-405](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L354-L405).

| Utility / Class | Target Structure / Purpose | Source Reference |
| :--- | :--- | :--- |
| `JSXNode` | Base structure for intrinsic and generic element nodes | [src/jsx/base.ts:338-352](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L338-L352) |
| `JSXFunctionNode` | Executes functional components and intrinsic tag renderers | [src/jsx/base.ts:255-296](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L296) |
| `JSXFragmentNode` | Renders fragment children without enclosing HTML tags | [src/jsx/base.ts:299-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L299-L303) |
| `isValidElement` | Type guard checking for `tag` and `props` properties | [src/jsx/base.ts:422-424](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L422-L424) |
| `cloneElement` | Clones elements with merged property overrides | [src/jsx/base.ts:426-443](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L426-L443) |
| `memo` | Wraps functional components with memoization and `DOM_MEMO` | [src/jsx/base.ts:384-405](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L384-L405) |

Sources: [src/jsx/base.ts:255-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L303), [src/jsx/base.ts:354-443](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L354-L443), [src/jsx/types.ts:19-23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/types.ts#L19-L23)

## Attribute Normalization and Escaping

### Attribute Validation and Processing

The runtime formats and serializes element attributes using the `jsxAttr` utility [src/jsx/jsx-runtime.ts:16-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L49). Before processing any value, `jsxAttr` validates the attribute name with `isValidAttributeName`; if validation fails, it returns an empty raw string [src/jsx/jsx-runtime.ts:20-22](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L20-L22). When the attribute name is valid, it initializes a string buffer formatted as `key="` [src/jsx/jsx-runtime.ts:23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L23).

```typescript
export const jsxAttr = (
  key: string,
  v: string | Promise<string> | Record<string, string | number | null | undefined | boolean>
): HtmlEscapedString | Promise<HtmlEscapedString> => {
  if (!isValidAttributeName(key)) {
    return raw('')
  }
  const buffer: StringBuffer = [`${key}="`] as StringBuffer
  // ...
```
Sources: [src/jsx/jsx-runtime.ts:16-23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L16-L23)

### Style Stringification and Value Types

Attribute values are handled differently based on their runtime types [src/jsx/jsx-runtime.ts:24-46](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L24-L46). If the attribute key is `style` and the value is an object, `styleObjectForEach` iterates over the style properties, skipping `null` or `undefined` entries, separating properties with semicolons, and escaping the resulting string into the buffer [src/jsx/jsx-runtime.ts:24-33](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L24-L33).

```typescript
if (key === 'style' && typeof v === 'object') {
  // object to style strings
  let styleStr = ''
  styleObjectForEach(v as Record<string, string | number>, (property, value) => {
    if (value != null) {
      styleStr += `${styleStr ? ';' : ''}${property}:${value}`
    }
  })
  escapeToBuffer(styleStr, buffer)
  buffer[0] += '"'
```
Sources: [src/jsx/jsx-runtime.ts:24-33](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L24-L33)

> [!NOTE]
> Null and undefined attribute values are suppressed entirely, returning an empty raw string instead of rendering empty attributes [src/jsx/jsx-runtime.ts:37-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L37-L38).

String values undergo HTML escaping via `escapeToBuffer` before closing quotation marks are appended [src/jsx/jsx-runtime.ts:34-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L34-L36). Numbers and already-escaped HTML strings append directly without re-escaping [src/jsx/jsx-runtime.ts:39-40](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L39-L40). Promise instances unshift the promise into the buffer for asynchronous resolution [src/jsx/jsx-runtime.ts:41-42](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L41-L42), while other types are converted to strings and escaped [src/jsx/jsx-runtime.ts:43-45](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L43-L45).

| Value Type | Processing Behavior | Source Reference |
| :--- | :--- | :--- |
| Invalid Name | Returns `raw('')` immediately | [src/jsx/jsx-runtime.ts:20-22](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L20-L22) |
| Style Object | Iterates properties, formats `property:value;` pairs, escapes buffer | [src/jsx/jsx-runtime.ts:24-33](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L24-L33) |
| `string` | Escapes string content via `escapeToBuffer` | [src/jsx/jsx-runtime.ts:34-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L34-L36) |
| `null` / `undefined` | Suppresses attribute, returns `raw('')` | [src/jsx/jsx-runtime.ts:37-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L37-L38) |
| `number` / `isEscaped` | Appends directly to buffer without escaping | [src/jsx/jsx-runtime.ts:39-40](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L39-L40) |
| `Promise` | Unshifts promise into buffer for async evaluation | [src/jsx/jsx-runtime.ts:41-42](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L41-L42) |

Sources: [src/jsx/jsx-runtime.ts:20-46](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/jsx-runtime.ts#L20-L46)

## Buffer Stringification Pipeline

### Node Serialization and Promise Interception

Tree serialization converts hierarchical node structures into contiguous string chunks using a mutable `StringBufferWithCallbacks` buffer. The process evaluates functional components through `JSXFunctionNode.toStringToBuffer`, invoking component functions with props and child elements [src/jsx/base.ts:255-264](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L255-L264). When a component return value is a `Promise`, the pipeline intercepts it to support asynchronous rendering [src/jsx/base.ts:269](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L269). If global contexts are active, the implementation captures current context bindings via `globalContexts.map` to preserve them across asynchronous yields [src/jsx/base.ts:273-274](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L273-L274). It then unshifts the promise into the buffer alongside an asynchronous callback that attaches captured local contexts to resolved node instances [src/jsx/base.ts:275-283](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L275-L283).

```typescript
} else if (res instanceof Promise) {
  if (globalContexts.length === 0) {
    buffer.unshift('', res)
  } else {
    // save current contexts for resuming
    const currentContexts: LocalContexts = globalContexts.map((c) => [c, c.values.at(-1)])
    buffer.unshift(
      '',
      res.then((childRes) => {
        if (childRes instanceof JSXNode) {
          childRes.localContexts = currentContexts
        }
        return childRes
      })
    )
  }
```
Sources: [src/jsx/base.ts:269-284](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L269-L284)

> [!WARNING]
> Async components executing without active global contexts omit the context-capturing map step, directly unshifting the promise to minimize overhead [src/jsx/base.ts:270-272](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L270-L272).

### Primitive Handling and Fragment Flattening

Non-promise component outputs undergo strict type checks before buffer insertion [src/jsx/base.ts:266-295](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L266-L295). Booleans, null, and undefined values yield immediate termination without appending anything to the buffer [src/jsx/base.ts:266-268](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L266-L268). Fragment nodes delegate traversal directly to `childrenToStringToBuffer` [src/jsx/base.ts:299-302](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L299-L302), flattening child collections without introducing wrapper elements.

```typescript
export class JSXFragmentNode extends JSXNode {
  override toStringToBuffer(buffer: StringBufferWithCallbacks): void {
    childrenToStringToBuffer(this.children, buffer)
  }
}
```
Sources: [src/jsx/base.ts:299-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L299-L303)]

Numbers and pre-escaped objects (`isEscaped`) append directly to the current buffer chunk, and any associated rendering callbacks are pushed onto the buffer's callback array [src/jsx/base.ts:287-292](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L287-L292). All other non-escaped primitives pass through `escapeToBuffer` to guarantee safety [src/jsx/base.ts:294](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L294).

| Return Type | Serialization Action | Source Reference |
| :--- | :--- | :--- |
| `boolean` / `null` / `undefined` | Discards output and returns immediately | [src/jsx/base.ts:266-268](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L266-L268) |
| `Promise` | Unshifts promise and context capture onto the buffer | [src/jsx/base.ts:269-284](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L269-L284) |
| `JSXNode` | Recursively invokes `toStringToBuffer` on the node | [src/jsx/base.ts:285-286](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L285-L286) |
| `number` / `isEscaped` | Appends directly and preserves evaluation callbacks | [src/jsx/base.ts:287-292](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L287-L292) |
| Other Types | Encodes value via `escapeToBuffer` | [src/jsx/base.ts:294](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L294) |

Sources: [src/jsx/base.ts:266-296](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L266-L296), [src/jsx/base.ts:299-303](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/base.ts#L299-L303)

## Child Collection Traversal

### Array Normalization and Children Object Utilities

The `toArray` utility normalizes child node inputs into an array representation, wrapping non-array elements in a single-element array while passing arrays through unchanged [src/jsx/children.ts:3-4](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L3-L4). The `Children` namespace aggregates utility methods designed for functional inspection and transformation of child node trees [src/jsx/children.ts:5-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L5-L20).

```typescript
export const toArray = (children: Child): Child[] =>
  Array.isArray(children) ? children : [children]
export const Children = {
  map: (children: Child[], fn: (child: Child, index: number) => Child): Child[] =>
    toArray(children).map(fn),
  forEach: (children: Child[], fn: (child: Child, index: number) => void): void => {
    toArray(children).forEach(fn)
  },
  count: (children: Child[]): number => toArray(children).length,
  only: (_children: Child[]): Child => {
    const children = toArray(_children)
    if (children.length !== 1) {
      throw new Error('Children.only() expects only one child')
    }
    return children[0]
  },
  toArray,
}
```
Sources: [src/jsx/children.ts:3-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L3-L20)

| Method | Parameters | Return Type | Description | Sources Reference |
| :--- | :--- | :--- | :--- | :--- |
| `toArray` | `children: Child` | `Child[]` | Normalizes single child or array into an array | [src/jsx/children.ts:3-4](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L3-L4) |
| `Children.map` | `children: Child[], fn: (child: Child, index: number) => Child` | `Child[]` | Maps over normalized child collection | [src/jsx/children.ts:6-7](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L6-L7) |
| `Children.forEach` | `children: Child[], fn: (child: Child, index: number) => void` | `void` | Iterates through normalized child collection | [src/jsx/children.ts:8-10](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L8-L10) |
| `Children.count` | `children: Child[]` | `number` | Returns the total count of normalized children | [src/jsx/children.ts:11](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L11) |
| `Children.only` | `_children: Child[]` | `Child` | Asserts exactly one child exists and returns it | [src/jsx/children.ts:12-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L12-L18) |
| `Children.toArray` | `children: Child` | `Child[]` | Alias to module-level `toArray` | [src/jsx/children.ts:19](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L19) |

Sources: [src/jsx/children.ts:3-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L3-L20)

> [!WARNING]
> `Children.only` evaluates normalized length and throws an explicit `Error` with message `'Children.only() expects only one child'` if the count is anything other than 1 [src/jsx/children.ts:12-17](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L12-L17).

Sources: [src/jsx/children.ts:12-18](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/children.ts#L12-L18)

## Client JSX Runtime Adapters

### DOM-Targeted JSX Factories and Server Emulation

The `hono/jsx/dom` runtime supplies adapters tailored for interactive client-side rendering and compatibility APIs mimicking `react-dom/server` [[src/jsx/dom/jsx-dev-runtime.ts:3-4], [src/jsx/dom/server.ts:3-4]]. The `jsxDEV` function constructs virtual DOM nodes while checking tag lookups against intrinsic element components [src/jsx/dom/jsx-dev-runtime.ts:10-13](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts#L10-L13). If a string tag matches an entry in `intrinsic-element/components`, the factory substitutes it before returning the descriptor object containing `tag`, `type`, `props`, `key`, and `ref` [src/jsx/dom/jsx-dev-runtime.ts:11-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts#L11-L20). The `Fragment` factory constructs an empty tag node utilizing `jsxDEV` with an empty string [src/jsx/dom/jsx-dev-runtime.ts:23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts#L23). The runtime exports `jsx`, `jsxs`, and `Fragment` through standard and dev entry modules [src/jsx/dom/jsx-runtime.ts:6-8](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-runtime.ts#L6-L8).

```typescript
export const jsxDEV = (tag: string | Function, props: Props, key?: string): JSXNode => {
  if (typeof tag === 'string' && intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {
    tag = intrinsicElementTags[tag as keyof typeof intrinsicElementTags]
  }
  return {
    tag,
    type: tag,
    props,
    key,
    ref: props.ref,
  } as JSXNode
}

export const Fragment = (props: Record<string, unknown>): JSXNode => jsxDEV('', props, undefined)
```
Sources: [src/jsx/dom/jsx-dev-runtime.ts:10-23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/jsx-dev-runtime.ts#L10-L23)

Server-side rendering adapters in `hono/jsx/dom/server` provide `renderToString` and `renderToReadableStream` functions [src/jsx/dom/server.ts:65](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L65). The `renderToString` implementation evaluates element stringification, logging a warning if options are provided, and throws an error if an asynchronous component is encountered [src/jsx/dom/server.ts:21-30](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L21-L30). The `renderToReadableStream` utility passes non-object elements through string conversion and delegates streaming output to `renderToReadableStreamHono` [src/jsx/dom/server.ts:50-63](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L50-L63).

```typescript
const renderToString = (element: Child, options: RenderToStringOptions = {}): string => {
  if (Object.keys(options).length > 0) {
    console.warn('options are not supported yet')
  }
  const res = element?.toString() ?? ''
  if (typeof res !== 'string') {
    throw new Error('Async component is not supported in renderToString')
  }
  return res
}
```
Sources: [src/jsx/dom/server.ts:21-30](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L21-L30)

> [!WARNING]
> `renderToString` throws an explicit `Error` with the message `'Async component is not supported in renderToString'` if the evaluated result is not a primitive string [src/jsx/dom/server.ts:26-28](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L26-L28).

Sources: [src/jsx/dom/server.ts:26-28](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/server.ts#L26-L28)]

### DOM Error Boundaries, Suspense, and Context

Specialized components for the DOM runtime handle asynchronous operations, boundary error trapping, and state context propagation [[src/jsx/dom/components.ts:7-38], [src/jsx/dom/context.ts:7-51]]. The `ErrorBoundary` component attaches a `DOM_ERROR_HANDLER` property to fragment nodes that intercepts errors, invokes optional `onError` callbacks, and returns fallback UI via `fallbackRender` or `fallback` props [src/jsx/dom/components.ts:7-23](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/components.ts#L7-L23). The `Suspense` component attaches a handler that checks whether caught errors are instances of `Promise`; if not, they are rethrown, otherwise `err.finally(retry)` is scheduled and the fallback child is returned [src/jsx/dom/components.ts:25-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/components.ts#L25-L38).

```typescript
export const ErrorBoundary: FC<
  PropsWithChildren<{
    fallback?: Child
    fallbackRender?: FallbackRender
    onError?: ErrorHandler
  }>
> = (({ children, fallback, fallbackRender, onError }: any) => {
  const res = Fragment({ children })
  ;(res as any)[DOM_ERROR_HANDLER] = (err: any) => {
    if (err instanceof Promise) {
      throw err
    }
    onError?.(err)
    return fallbackRender?.(err) || fallback
  }
  return res
}) as any

export const Suspense: FC<PropsWithChildren<{ fallback: any }>> = (({
  children,
  fallback,
}: any) => {
  const res = Fragment({ children })
  ;(res as any)[DOM_ERROR_HANDLER] = (err: any, retry: () => void) => {
    if (!(err instanceof Promise)) {
      throw err
    }
    err.finally(retry)
    return fallback
  }
  return res
}) as any
```
Sources: [src/jsx/dom/components.ts:7-38](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/components.ts#L7-L38)

Context providers use `createContextProviderFunction` to push and pop context values into an internal array during tree traversal [src/jsx/dom/context.ts:7-43](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/context.ts#L7-L43). When rendering, the provider injects internal tag nodes that mutate the shared values array before and after evaluating children, and registers an error handler to pop values if rendering throws [src/jsx/dom/context.ts:17-42](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/context.ts#L17-L42). The `createContext` function initializes the default value array, binds `Provider`, and registers the context inside `globalContexts` [src/jsx/dom/context.ts:45-51](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/context.ts#L45-L51).

```typescript
export const createContextProviderFunction =
  <T>(values: T[]): Function =>
  ({ value, children }: { value: T; children: Child[] }) => {
    if (!children) {
      return undefined
    }

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const props: { children: any } = {
      children: [
        {
          tag: setInternalTagFlag(() => {
            values.push(value)
          }),
          props: {},
        },
      ],
    }
    if (Array.isArray(children)) {
      props.children.push(...children.flat())
    } else {
      props.children.push(children)
    }
    props.children.push({
      tag: setInternalTagFlag(() => {
        values.pop()
      }),
      props: {},
    })
    const res = { tag: '', props, type: '' }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ;(res as any)[DOM_ERROR_HANDLER] = (err: unknown) => {
      values.pop()
      throw err
    }
    return res
  }
```
Sources: [src/jsx/dom/context.ts:7-43](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/context.ts#L7-L43)

## Related

- [JSX Components and Context](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-components-and-context)
- [JSX Streaming and Suspense](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-streaming-and-suspense)
- [Client DOM and Hooks](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/client-dom-and-hooks)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
