JavaScript Tagged Template Literals Deep Dive
A deep dive into JavaScript tagged template literals. Covers tag function anatomy, the strings and values parameters, raw strings, building custom HTML sanitizers, i18n formatters, CSS-in-JS helpers, SQL query builders, and advanced patterns for DSL creation with template tags.
Tagged template literals let you process a template string with a custom function. The tag function receives the static string parts and the interpolated values as separate arguments, giving you full control over how the final output is assembled. This is the mechanism behind libraries like styled-components, graphql-tag, and html template engines.
How Tag Functions Work
A tag function is called with the template literal placed after its name, with no parentheses:
function tag(strings, ...values) {
console.log(strings); // Array of static string parts
console.log(values); // Array of interpolated values
}
const name = "Alice";
const age = 30;
tag`Hello, ${name}! You are ${age} years old.`;
// strings: ["Hello, ", "! You are ", " years old."]
// values: ["Alice", 30]The strings array always has one more element than values. The static parts surround the interpolated expressions.
Building the Output
Most tag functions iterate strings and values to produce a result:
function highlight(strings, ...values) {
let result = "";
strings.forEach((str, i) => {
result += str;
if (i < values.length) {
result += `**${values[i]}**`;
}
});
return result;
}
const item = "JavaScript";
const count = 42;
highlight`Learn ${item} in ${count} tutorials`;
// "Learn **JavaScript** in **42** tutorials"The strings.raw Property
The strings parameter has a raw property containing the raw (unescaped) versions of each string part:
function showRaw(strings) {
console.log("cooked:", strings[0]); // interprets \n as newline
console.log("raw:", strings.raw[0]); // keeps \n as literal characters
}
showRaw`Hello\nWorld`;
// cooked: Hello
// World
// raw: Hello\nWorldJavaScript provides a built-in String.raw tag that returns the raw string:
const path = String.raw`C:\Users\alice\documents`;
// "C:\\Users\\alice\\documents" — backslashes preservedPractical Pattern: HTML Sanitizer
Prevent XSS by escaping interpolated values:
function safeHtml(strings, ...values) {
const escape = (str) =>
String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
let result = "";
strings.forEach((str, i) => {
result += str;
if (i < values.length) {
result += escape(values[i]);
}
});
return result;
}
const userInput = '<script>alert("xss")</script>';
const html = safeHtml`<div class="comment">${userInput}</div>`;
// '<div class="comment"><script>alert("xss")</script></div>'The static template parts are trusted (written by the developer), while interpolated values are escaped.
Practical Pattern: SQL Query Builder
Build parameterized queries to prevent SQL injection:
function sql(strings, ...values) {
let query = "";
const params = [];
strings.forEach((str, i) => {
query += str;
if (i < values.length) {
params.push(values[i]);
query += `$${params.length}`;
}
});
return { query, params };
}
const userId = 42;
const status = "active";
const result = sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`;
// { query: "SELECT * FROM users WHERE id = $1 AND status = $2", params: [42, "active"] }| Approach | Injection Safe? | Readable? |
|---|---|---|
| String concatenation | No | Somewhat |
| Parameterized query (manual) | Yes | Verbose |
| Tagged template SQL | Yes | Very readable |
Practical Pattern: i18n Formatter
Format localized strings with automatic number and date formatting:
function i18n(strings, ...values) {
const locale = "en-US";
let result = "";
strings.forEach((str, i) => {
result += str;
if (i < values.length) {
const val = values[i];
if (typeof val === "number") {
result += new Intl.NumberFormat(locale).format(val);
} else if (val instanceof Date) {
result += new Intl.DateTimeFormat(locale).format(val);
} else {
result += String(val);
}
}
});
return result;
}
const price = 1234567.89;
const date = new Date("2026-03-06");
i18n`Total: ${price} as of ${date}`;
// "Total: 1,234,567.89 as of 3/6/2026"Practical Pattern: CSS-in-JS
Create scoped CSS strings with dynamic values:
function css(strings, ...values) {
let result = "";
strings.forEach((str, i) => {
result += str;
if (i < values.length) {
const val = values[i];
result += typeof val === "number" ? `${val}px` : val;
}
});
return result.trim();
}
const padding = 16;
const color = "#3b82f6";
const styles = css`
.card {
padding: ${padding};
color: ${color};
border-radius: ${8};
}
`;
// ".card { padding: 16px; color: #3b82f6; border-radius: 8px; }"Returning Non-Strings
Tag functions can return any type, not just strings:
function toArray(strings, ...values) {
const result = [];
strings.forEach((str, i) => {
if (str) result.push(str.trim());
if (i < values.length) result.push(values[i]);
});
return result;
}
toArray`name: ${"Alice"}, age: ${30}`;
// ["name:", "Alice", ", age:", 30]Libraries use this to return AST nodes, DOM elements, or framework-specific objects.
Nesting Tagged Templates
Tag functions compose naturally:
const header = safeHtml`<h1>${title}</h1>`;
const body = safeHtml`<p>${content}</p>`;
const page = safeHtml`
<div class="page">
${header}
${body}
</div>
`;Each inner tag processes its own interpolations, and the outer tag receives the already-processed result.
Comparison With Regular Template Literals
| Feature | Regular Template Literal | Tagged Template Literal |
|---|---|---|
| Syntax | `Hello $\{name\}` | tag`Hello $\{name\}` |
| Result type | Always a string | Any type (tag decides) |
| Interpolation | Automatic toString() | Tag controls processing |
| Use case | Simple string formatting | Sanitization, DSLs, parsing |
| Raw access | Not available | strings.raw |
Rune AI
Key Insights
- Tag functions receive strings and values separately: Static parts are in the strings array; interpolated expressions are in the values rest parameter
- strings.raw provides unescaped string parts: Useful for paths, regex patterns, and any content where backslashes should be preserved
- Tag functions can return any type: Strings, arrays, DOM nodes, AST objects, or framework-specific values
- HTML sanitization is the classic use case: Escape interpolated user input while trusting the developer-written template structure
- The strings reference is stable per call site: The same template literal location always passes the same strings array reference, enabling caching
Frequently Asked Questions
Can I use async tag functions?
What happens if the tag function is undefined?
Can I create a tag that caches results?
Why does strings always have one more element than values?
Can tagged templates replace regular expressions for parsing?
Conclusion
Tagged template literals are one of JavaScript's most powerful metaprogramming features. They let you intercept template string processing to build HTML sanitizers, SQL builders, CSS-in-JS systems, i18n formatters, and domain-specific languages. The key insight is that static parts and dynamic values arrive as separate arguments, enabling safe-by-default string composition. For the module system used to share tag functions, see JavaScript ES6 modules import export guide. For the spread/rest syntax often used in tag implementations, see JS spread vs rest operator complete tutorial.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.