Hono provides helpers for writing HTML with tagged template literals and experimental CSS-in-JS functionality with scoped styles and SSR support.
HTML Helper
The html helper allows you to write HTML using tagged template literals with automatic escaping.
Import
import { html, raw } from 'hono/html'
Basic Usage
import { html } from 'hono/html'
app.get('/', (c) => {
const name = 'World'
return c.html(
html`
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello ${name}!</h1>
</body>
</html>
`
)
})
Automatic Escaping
String values are automatically escaped for security:
import { html } from 'hono/html'
app.get('/user', (c) => {
const userInput = '<script>alert("XSS")</script>'
return c.html(
html`
<div>
User input: ${userInput}
</div>
`
)
})
// Output: User input: <script>alert("XSS")</script>
Values are escaped by default to prevent XSS attacks. Use the raw helper only when you trust the content.
Raw HTML
When you need to insert unescaped HTML:
import { html, raw } from 'hono/html'
app.get('/article', (c) => {
const trustedHtml = '<p>This is <strong>safe</strong> HTML</p>'
return c.html(
html`
<div class="article">
${raw(trustedHtml)}
</div>
`
)
})
Only use raw() with trusted content. Never use it with user-generated content as it bypasses XSS protection.
Value Types
The html helper handles various types:
import { html } from 'hono/html'
app.get('/types', (c) => {
return c.html(
html`
<ul>
<li>String: ${'text'}</li>
<li>Number: ${42}</li>
<li>Boolean: ${true}</li>
<li>Null: ${null}</li>
<li>Undefined: ${undefined}</li>
</ul>
`
)
})
// Boolean, null, and undefined are not rendered
Arrays
Arrays are automatically flattened:
import { html } from 'hono/html'
app.get('/list', (c) => {
const items = ['Apple', 'Banana', 'Cherry']
return c.html(
html`
<ul>
${items.map(item => html`<li>${item}</li>`)}
</ul>
`
)
})
Async Content
Supports Promise values for async rendering:
import { html } from 'hono/html'
app.get('/async', async (c) => {
const fetchData = async () => {
// Fetch data from API
return 'Fetched content'
}
return c.html(
html`
<div>
${fetchData()}
</div>
`
)
})
CSS Helper
The CSS helper is experimental. The API may change in future versions.
The CSS helper provides CSS-in-JS functionality with automatic class name generation and scoped styles.
Import
import { css, cx, keyframes, Style } from 'hono/css'
Basic Styling
import { css, Style } from 'hono/css'
import { html } from 'hono/html'
app.get('/', (c) => {
const className = css`
color: blue;
font-size: 16px;
&:hover {
color: red;
}
`
return c.html(
html`
<!DOCTYPE html>
<html>
<head>
<${Style} />
</head>
<body>
<h1 class="${className}">Styled text</h1>
</body>
</html>
`
)
})
The <Style /> component must be included in the document head to inject generated styles.
CSS Variables
Use JavaScript variables in your styles:
import { css } from 'hono/css'
app.get('/dynamic', (c) => {
const color = 'blue'
const size = 20
const className = css`
color: ${color};
font-size: ${size}px;
`
return c.html(html`<div class="${className}">Dynamic styling</div>`)
})
Combining Classes
Use cx to combine multiple class names:
import { css, cx } from 'hono/css'
app.get('/combined', (c) => {
const base = css`
padding: 10px;
`
const primary = css`
background: blue;
color: white;
`
const className = cx(base, primary)
return c.html(html`<button class="${className}">Click me</button>`)
})
Conditional Classes
cx supports conditional classes:
import { css, cx } from 'hono/css'
app.get('/conditional', (c) => {
const base = css`padding: 10px;`
const active = css`background: blue;`
const isActive = true
const className = cx(base, isActive && active)
return c.html(html`<div class="${className}">Content</div>`)
})
Keyframes
Define CSS animations:
import { css, keyframes, Style } from 'hono/css'
import { html } from 'hono/html'
app.get('/animation', (c) => {
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const className = css`
animation: ${fadeIn} 1s ease-in;
`
return c.html(
html`
<!DOCTYPE html>
<html>
<head>
<${Style} />
</head>
<body>
<div class="${className}">Fading in...</div>
</body>
</html>
`
)
})
View Transitions
Support for View Transitions API:
import { css, viewTransition, Style } from 'hono/css'
import { html } from 'hono/html'
app.get('/transition', (c) => {
const transitionName = viewTransition`slide`
const className = css`
view-transition-name: ${transitionName};
`
return c.html(
html`
<!DOCTYPE html>
<html>
<head>
<${Style} />
</head>
<body>
<div class="${className}">Transitioning element</div>
</body>
</html>
`
)
})
Multiple CSS Contexts
Create separate CSS contexts for isolation:
import { createCssContext } from 'hono/css'
const { css: css1, Style: Style1 } = createCssContext({ id: 'app1' })
const { css: css2, Style: Style2 } = createCssContext({ id: 'app2' })
app.get('/context1', (c) => {
const className = css1`color: blue;`
return c.html(
html`
<html>
<head><${Style1} /></head>
<body><div class="${className}">App 1</div></body>
</html>
`
)
})
CSP Nonce Support
Add nonce for Content Security Policy:
import { Style } from 'hono/css'
import { html } from 'hono/html'
app.get('/secure', (c) => {
const nonce = 'random-nonce-value'
return c.html(
html`
<!DOCTYPE html>
<html>
<head>
<${Style} nonce="${nonce}" />
</head>
<body>
<div>Secure content</div>
</body>
</html>
`
)
})
Raw CSS Strings
For advanced use cases, work with raw CSS strings:
import { rawCssString } from 'hono/css'
const cssString = rawCssString`
.custom {
color: blue;
}
`
Best Practices
Always Escape User Input
Use the default escaping behavior for all user-generated content
Include Style Component
Remember to add <Style /> in the document head when using CSS helper
Reuse Classes
Define common styles once and reuse them with cx()
Test Browser Support
CSS helper is experimental - test thoroughly in your target browsers
The CSS helper automatically deduplicates styles and only injects each class once, even if used multiple times.
SSR Support
Both helpers work seamlessly with server-side rendering, with styles injected inline for immediate availability.