Skip to main content
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: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;
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

Performance

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.

Build docs developers (and LLMs) love