HTML Template Overview
The html-template package provides a single tagged template literal --- html --- that generates HTML with automatic escaping of all interpolated values. It is the simplest way to produce HTML in Remix V3 when you do not need a component system.
What is a tagged template literal?
A tagged template is a JavaScript feature where a function processes a template string and its interpolations. The html tag receives your template, escapes every interpolated value to prevent XSS, and returns a SafeHtml object.
Key Features
| Feature | What It Does |
|---|---|
| Auto-escaping | Every interpolated value is HTML-escaped by default. <script> becomes <script>. |
| SafeHtml type | The return type is SafeHtml, a branded string. When you nest one html result inside another, it is not double-escaped. |
| Composable | Build pages from small template functions. Each returns SafeHtml and composes naturally. |
| No dependencies | Zero runtime dependencies. Works in any JavaScript environment. |
Quick Example
import { html } from 'remix/html-template'
let userInput = '<script>alert("xss")</script>'
let page = html`
<!DOCTYPE html>
<html>
<body>
<h1>Hello</h1>
<p>${userInput}</p>
</body>
</html>
`
// The script tag is escaped:
// <p><script>alert("xss")</script></p>XSS Prevention
Cross-Site Scripting (XSS) happens when untrusted input is inserted into HTML without escaping. The html tag prevents this automatically:
// SAFE: html tag escapes the value
let safe = html`<div>${userComment}</div>`
// DANGEROUS: plain template literal does not escape
let dangerous = `<div>${userComment}</div>`What is XSS?
Cross-Site Scripting (XSS) is an attack where a malicious user injects JavaScript into a web page that other users view. If a comment field allows <script> tags, an attacker can steal cookies or redirect users. The html tag escapes all dangerous characters so injected scripts are displayed as plain text.
The following characters are escaped:
| Character | Escaped As |
|---|---|
& | & |
< | < |
> | > |
" | " |
' | ' |
Composing Templates
SafeHtml values are not double-escaped when nested. This makes it natural to break pages into functions:
function renderHeader(title: string) {
return html`<header><h1>${title}</h1></header>`
}
function renderPage(title: string, body: string) {
return html`
<!DOCTYPE html>
<html>
<head><title>${title}</title></head>
<body>
${renderHeader(title)}
<main>${body}</main>
</body>
</html>
`
}renderHeader returns SafeHtml, so it is inserted as-is into the outer template. The title and body strings are escaped.
The isSafeHtml Guard
Use isSafeHtml to check whether a value has already been escaped:
import { html, isSafeHtml } from 'remix/html-template'
isSafeHtml(html`<p>safe</p>`) // true
isSafeHtml('<p>plain</p>') // falseWhen to Use html-template vs. component
| Use Case | Package |
|---|---|
| Simple server-rendered pages, emails, RSS feeds | html-template |
| Interactive UIs, client hydration, animations | component |
| Mixing both in one app | Works fine --- html-template for static pages, component for interactive ones |
Related
- HTML Template Tutorial --- Build pages, compose templates, and handle user input safely.
- HTML Template Reference --- Full API reference.
- Component Overview --- The JSX-based alternative for interactive UIs.
- Security Guide --- Broader security best practices.