Counter component example with hono/jsx/dom
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
function App() {
return (
<html>
<body>
<Counter />
</body>
</html>
)
}
const root = document.getElementById('root')
render(<App />, root)
hono/jsx/dom client-side JSX
hono/jsx supports client-side interactive UI in the browser through Client Components or hono/jsx/dom. A counter program in hono/jsx/dom is only 2.8KB with Brotli compression, compared to 47.8KB for React.
render() function for mounting components
The render() function inserts JSX components within a specified HTML element. It takes the component and a container element as arguments: render(<Component />, container).
React-compatible hooks in hono/jsx/dom
hono/jsx/dom provides the following hooks that are compatible or partially compatible with React: useState(), useEffect(), useRef(), useCallback(), use(), startTransition(), useTransition(), useDeferredValue(), useMemo(), useLayoutEffect(), useReducer(), useDebugValue(), createElement(), memo(), isValidElement(), useId(), createRef(), forwardRef(), useImperativeHandle(), useSyncExternalStore(), useInsertionEffect(), useFormStatus(), useActionState(), and useOptimistic().
startViewTransition() function
The startViewTransition() function wraps document.startViewTransition for a shorter syntax when handling View Transitions API. It takes a callback function that updates state or performs DOM changes within the transition.
viewTransition() CSS helper function
The viewTransition() function from hono/jsx/dom/css generates a unique view-transition-name that can be used in CSS with keyframes. It converts ::view-transition-old() and ::view-transition-new() pseudo-elements automatically.
useViewTransition() hook
The useViewTransition() hook returns a tuple of [boolean, (callback: () => void) => void] representing the isUpdating flag and startViewTransition() function. The component is evaluated when startViewTransition() is called and when the finish promise becomes fulfilled. This allows conditional styling only during the animation.
Configure hono/jsx/dom in tsconfig.json
To use the smaller hono/jsx/dom runtime instead of hono/jsx, set jsxImportSource to "hono/jsx/dom" and jsx to "react-jsx" in tsconfig.json compilerOptions.
Configure hono/jsx/dom in vite.config.ts
Alternatively, specify hono/jsx/dom for JSX transformation in vite.config.ts by setting jsxImportSource to "hono/jsx/dom" in the esbuild options.
Render JSX with c.html()
Use c.html() to render JSX components in route handlers. Pass the JSX element as an argument: return c.html(<ComponentName prop={value} />)
JSX tsconfig.json configuration
To use JSX in Hono, modify tsconfig.json with these compiler options: "jsx": "react-jsx" and "jsxImportSource": "hono/jsx". Alternatively, use pragma directives /** @jsx jsx */ and /** @jsxImportSource hono/jsx */ at the top of files.
JSX configuration for Deno
For Deno, modify deno.json instead of tsconfig.json with compiler options: "jsx": "precompile" and "jsxImportSource": "@hono/hono/jsx".
JSX file extension requirement
When using JSX with Hono, change the main file extension from .ts to .tsx. Also update package.json (or deno.json) dev scripts to reflect this change, for example from "bun run --hot src/index.ts" to "bun run --hot src/index.tsx".
JSX component with FC type
Import FC from 'hono/jsx' to type functional components. FC is the functional component type that can include generic type parameters for props. Example: const MyComponent: FC<{ messages: string[] }> = (props) => { return ... }
Metadata hoisting in JSX
Document metadata tags such as <title>, <link>, and <meta> can be written directly inside components and will be automatically hoisted to the <head> section of the document. When hoisting occurs, existing elements are not removed; elements appearing later are added to the end.
Fragment and empty tag syntax
Use Fragment from 'hono/jsx' or <></> syntax to group multiple elements without adding extra nodes. Both approaches work: <Fragment> ... </Fragment> or <> ... </>
PropsWithChildren type
Import PropsWithChildren from 'hono/jsx' to correctly infer child elements in function components. Usage: function Component({ title, children }: PropsWithChildren<CustomType>) { ... }
dangerouslySetInnerHTML for raw HTML
To directly insert raw HTML in JSX, use the dangerouslySetInnerHTML attribute with an object containing __html property: const inner = { __html: 'JSX · SSR' }; const element = <div dangerouslySetInnerHTML={inner} />
memo for component memoization
Import memo from 'hono/jsx' to optimize components by memoizing computed strings. Usage: const Header = memo(() => <header>Welcome to Hono</header>)
useContext for sharing data across component tree
Use createContext and useContext from 'hono/jsx' to share data globally across component trees without passing values through props. Create context with createContext(), access it with useContext(), and provide values with Context.Provider component.
Async components in JSX
hono/jsx supports async components using async/await syntax. When rendering with c.html(), async components are automatically awaited. Example: const AsyncComponent = async () => { await delay(); return <div>Done!</div> }
Suspense for streaming components
Use Suspense from 'hono/jsx/streaming' to wrap async components for streaming. Content in fallback renders first, then awaited content displays once the Promise resolves. Must use renderToReadableStream() for rendering. Mark as experimental feature.
renderToReadableStream for streaming
Import renderToReadableStream from 'hono/jsx/streaming' to render JSX to a readable stream. Use with Suspense for streaming support. Return with c.body() setting headers 'Content-Type': 'text/html; charset=UTF-8' and 'Transfer-Encoding': 'chunked'.
ErrorBoundary for error handling
Use ErrorBoundary component to catch errors in child components. Specify fallback content to display if an error occurs. ErrorBoundary works with both sync and async components, and can be combined with Suspense. Mark as experimental feature.
StreamingContext for streaming configuration
Use StreamingContext from 'hono/jsx/streaming' to provide configuration for streaming components like Suspense and ErrorBoundary. Useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP). Set scriptNonce value that is automatically added to <script> tags. Mark as experimental feature.
JSX with html middleware
Combine JSX and html middleware (from 'hono/html') for powerful templating. The html middleware can be used alongside JSX components for template creation.
JSX Renderer Middleware
The JSX Renderer Middleware allows creating HTML pages more easily with JSX, providing a built-in middleware for JSX rendering.
Override JSX type definitions
Override JSX type definitions to add custom elements and attributes by declaring module 'hono/jsx' with custom IntrinsicElements in the JSX namespace. Example: declare module 'hono/jsx' { namespace JSX { interface IntrinsicElements { 'my-custom-element': HTMLAttributes & { 'x-event'?: 'click' | 'scroll' } } } }
Example: Basic JSX with Hono
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
<html>
<body>{props.children}</body>
</html>
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
<Layout>
<h1>Hello Hono!</h1>
<ul>
{props.messages.map((message) => {
return <li>{message}!!</li>
})}
</ul>
</Layout>
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html(<Top messages={messages} />)
})
export default app
Example: Metadata hoisting with setRenderer
import { Hono } from 'hono'
const app = new Hono()
app.use('*', async (c, next) => {
c.setRenderer((content) => {
return c.html(
<html>
<head></head>
<body>{content}</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>About Page</title>
<meta name='description' content='This is the about page.' />
about page content
</>
)
})
export default app
Example: Context usage in JSX
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: { color: '#000000', background: '#eeeeee' },
dark: { color: '#ffffff', background: '#222222' },
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return <button style={theme}>Push!</button>
}
const Toolbar: FC = () => {
return (
<div>
<Button />
</div>
)
}
app.get('/', (c) => {
return c.html(
<div>
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
</div>
)
})
Example: Async component with Suspense
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000))
return <div>Done!</div>
}
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<Suspense fallback={<div>loading...</div>}>
<AsyncComponent />
</Suspense>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})
Example: ErrorBoundary usage
function SyncComponent() {
throw new Error('Error')
return <div>Hello</div>
}
app.get('/sync', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<SyncComponent />
</ErrorBoundary>
</body>
</html>
)
})
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return <div>Hello</div>
}
app.get('/with-suspense', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
</body>
</html>
)
})
Example: StreamingContext with nonce
import { Suspense, StreamingContext } from 'hono/jsx/streaming'
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<StreamingContext
value={{ scriptNonce: 'random-nonce-value' }}
>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</StreamingContext>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
'Content-Security-Policy':
"script-src 'nonce-random-nonce-value'",
},
})
})
CSS helpers security and injection prevention
The css helpers are raw CSS sinks. They block breaking out into HTML by filtering quotes, backslashes, and </, but {}, and ; pass through as valid CSS. Do not pass untrusted input directly. Validate input against an allowlist before using it in css template literals.
css helper import and basic usage
Import the css helper from 'hono/css'. The css function is a template literal that writes CSS in JSX. It returns a class name that is set as the value of the class attribute. The <Style /> component must be included to render the CSS content.
css pseudo-class syntax with nesting selector
Use the CSS nesting selector & to style pseudo-classes like :hover within a css template literal. Example: css`background-color: #fff; &:hover { background-color: red; }`
css class extension and composition
You can extend CSS definitions by embedding a class name in another css template literal. Use the syntax ${baseClass} to reference a previously defined class, and you can nest selectors with ${headerClass} { selector { } } syntax to apply styles to nested elements.
keyframes for CSS animations
The keyframes function writes the contents of @keyframes rules. The return value is the animation name that can be used in the animation-name property of a css template literal.
:-hono-global pseudo-selector for global styles
The :-hono-global pseudo-selector allows you to define global styles within a css template literal. Alternatively, you can pass css literals directly to the <Style /> component to define global styles.
cx function composites class names
The cx function composes two or more class names together. It can combine css template literal results and simple string class names. Example: cx(buttonClass, primaryClass) or cx('h1', primaryClass)
Style component with nonce for CSP headers
When using the css helpers with the Secure Headers middleware and Content-Security-Policy, add the nonce attribute to the <Style /> component: <Style nonce={c.get('secureHeadersNonce')} />. Set styleSrc to [NONCE] in the secureHeaders middleware configuration.
createCssContext for custom CSS configuration
createCssContext creates custom css, cx, keyframes, viewTransition, and Style functions. It accepts options: id (custom style element ID), classNameSlug (function to generate custom class names), and onInvalidSlug (function to handle invalid class names).
classNameSlug function signature and parameters
The classNameSlug function receives three arguments: hash (default generated class name like 'css-1234567890'), label (extracted from a comment at the start of the CSS template, or empty string), and css (the minified CSS string). Return a custom class name string.
onInvalidSlug callback for invalid class names
The onInvalidSlug option is a function that receives the invalid slug as an argument. By default, a warning is logged. You can customize this to throw an error or handle it differently.
html helper in JSX for inline scripts
Insert html template literals directly into JSX to embed inline scripts without needing dangerouslySetInnerHTML. The script content will not be escaped when written inside html`` tags within JSX.
Extending ContextRenderer for custom props
Extend ContextRenderer interface in Hono module to pass additional props to renderer. Declare module 'hono' with interface ContextRenderer accepting content and custom props object. Example: interface ContextRenderer { (content: string | Promise<string>, props: { title: string }): Response }. Then use c.render(jsx, { title: 'value' }) to pass props.
JSX Renderer Middleware import
Import JSX Renderer Middleware with `import { Hono } from 'hono'` and `import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'`.
jsxRenderer basic usage
Use jsxRenderer as middleware to set up layout for c.render(). Pass a function that receives {children} and returns JSX wrapping the children. Example: app.get('/page/*', jsxRenderer(({ children }) => <html><body><header>Menu</header><div>{children}</div></body></html>)). Then call c.render() with JSX content in route handlers.
jsxRenderer docType option
The docType option is optional and accepts boolean or string. If false, no DOCTYPE is added to the HTML. If a string, the custom DOCTYPE is used instead of the default. Default behavior adds a DOCTYPE.
jsxRenderer stream option
The stream option is optional and accepts boolean or Record<string, string>. If true or a Record value is provided, renders as streaming response. When true, adds headers: 'Transfer-Encoding': 'chunked', 'Content-Type': 'text/html; charset=UTF-8', 'Content-Encoding': 'Identity'. Record values customize header values.
jsxRenderer stream with async components
Stream option enables rendering async components with Suspense. Example: jsxRenderer(({ children }) => <html><body><h1>SSR Streaming</h1>{children}</body></html>, { stream: true }). Components can use async/await and Suspense fallback.
jsxRenderer function-based options
Instead of a static options object, pass a function that receives Context object. This allows dynamic option setting based on request context. Example: jsxRenderer(layoutFn, (c) => ({ stream: c.req.header('X-Enable-Streaming') === 'true' })). Useful for enabling/disabling features based on request headers or environment.
jsxRenderer with SSG context
Use isSSGContext helper with function-based options to disable streaming during static site generation: jsxRenderer(layoutFn, (c) => ({ stream: !isSSGContext(c) })). This prevents streaming when generating static sites.
Nested layouts with Layout component
Enable nested layouts by using the Layout component prop. Parent layout applies jsxRenderer() with standard layout. Child app applies jsxRenderer with Layout prop: jsxRenderer(({ children, Layout }) => <Layout><nav>Blog Menu</nav><div>{children}</div></Layout>). Mount child app to parent with app.route('/blog', blog).
useRequestContext hook
useRequestContext() returns the Context instance from within JSX components. Use to access request data like url: const c = useRequestContext(); return <b>{c.req.url}</b>. Enables components to access request context without passing through props.
useRequestContext incompatibility with Deno precompile JSX
useRequestContext() cannot be used with Deno's precompile JSX option. Use 'react-jsx' instead in tsconfig.json: set compilerOptions.jsx to 'react-jsx' and jsxImportSource to 'hono/jsx'.
c.render with extended ContextRenderer props
Call c.render(jsx, { customProps }) to pass additional properties to the renderer layout function. Example: c.render(<div>content</div>, { title: 'My favorites' }) where layout receives title in destructuring: jsxRenderer(({ children, title }) => ...).