The HTML Helper provides a tagged template literal for creating HTML strings with automatic XSS protection through HTML escaping.
Import
import { html, raw } from 'hono/html'
Functions
html()
Creates an HTML string with automatic escaping of interpolated values.
function html(
strings: TemplateStringsArray,
...values: unknown[]
): HtmlEscapedString | Promise<HtmlEscapedString>
strings
TemplateStringsArray
required
The template string array
Values to interpolate into the template. All values are automatically HTML-escaped except those wrapped with raw().
return
HtmlEscapedString | Promise<HtmlEscapedString>
An HTML-escaped string. Returns a Promise if any interpolated value is a Promise.
Example
import { html } from 'hono/html'
app.get('/', (c) => {
const name = 'John <script>alert("xss")</script>'
// Values are automatically escaped
const content = html`
<html>
<body>
<h1>Hello ${name}!</h1>
</body>
</html>
`
// Output: <h1>Hello John <script>alert("xss")</script>!</h1>
return c.html(content)
})
Escaping Behavior
The html helper automatically escapes the following:
- Strings: HTML special characters are escaped (
<, >, &, ", ')
- Numbers: Rendered as-is
- Booleans, null, undefined: Ignored (rendered as empty string)
- Arrays: Flattened with
Array.flat(Infinity) and each element is processed
- Promises: Awaited and then processed
- Objects: Converted to string via
.toString() and escaped
app.get('/demo', (c) => {
const values = [
'Name: John',
['<b>Bold</b>', undefined, null],
' Contact:',
[html`<a href="/">Link</a>`]
]
return c.html(html`<div>${values}</div>`)
// Output: <div>Name: John<b>Bold</b> Contact:<a href="/">Link</a></div>
})
Async Support
When interpolating Promises, the html function returns a Promise:
app.get('/async', async (c) => {
const fetchUserName = async () => {
// Fetch from database
return 'John Doe'
}
const content = html`
<html>
<body>
<h1>User: ${fetchUserName()}</h1>
</body>
</html>
`
// content is a Promise, so await it
return c.html(await content)
})
raw()
Marks a string as safe HTML that should not be escaped.
function raw(value: string): HtmlEscapedString
The HTML string that should not be escaped
An HTML string marked as safe (not escaped)
Example
import { html, raw } from 'hono/html'
app.get('/with-raw', (c) => {
const userInput = '<script>alert("xss")</script>'
const safeHTML = '<strong>Important</strong>'
return c.html(html`
<div>
<!-- This will be escaped -->
<p>${userInput}</p>
<!-- This will NOT be escaped -->
<p>${raw(safeHTML)}</p>
</div>
`)
})
Output:
<div>
<!-- Escaped -->
<p><script>alert("xss")</script></p>
<!-- Not escaped -->
<p><strong>Important</strong></p>
</div>
Use Cases
Building Components
const Layout = (props: { title: string; children: any }) => html`
<!DOCTYPE html>
<html>
<head>
<title>${props.title}</title>
</head>
<body>
${props.children}
</body>
</html>
`
const Content = () => html`
<div>
<h1>Welcome</h1>
<p>This is content</p>
</div>
`
app.get('/', (c) => {
return c.html(
Layout({
title: 'My Site',
children: Content()
})
)
})
Conditional Rendering
app.get('/user/:id', (c) => {
const user = getUser(c.req.param('id'))
return c.html(html`
<div>
${user
? html`<h1>Hello ${user.name}</h1>`
: html`<p>User not found</p>`
}
</div>
`)
})
Lists
app.get('/items', (c) => {
const items = ['Apple', 'Banana', 'Cherry']
return c.html(html`
<ul>
${items.map(item => html`<li>${item}</li>`)}
</ul>
`)
})
Security
The html helper provides automatic XSS protection by escaping all interpolated values by default. Only use raw() when you are certain the content is safe.
// ✅ SAFE: User input is escaped
html`<div>${userInput}</div>`
// ⚠️ DANGEROUS: User input is not escaped
html`<div>${raw(userInput)}</div>`
// ✅ SAFE: Sanitized content
html`<div>${raw(sanitizeHTML(userInput))}</div>`
Types
interface HtmlEscapedString {
isEscaped: true
callbacks?: HtmlEscapedCallback[]
}
interface HtmlEscaped {
isEscaped: true
}