Tagged template literals
A tagged template literal is a template literal with a function name written in front of it; instead of joining the pieces automatically, JavaScript calls that function and hands it the string parts and the injected values separately. The tag decides what to do with the values, which is why this construct is used for escaping, formatting and custom mini-languages such as CSS-in-JS.
Theory
TL;DR
- A plain template just inserts values, a tagged one hands control to a function.
- The tag function receives two sets of data: the array of text parts and the list of injected values.
tag`Hello, ${name}!`runs astag(['Hello, ', '!'], name).- A tag may return anything, not necessarily a string.
- Main uses: safe HTML, CSS-in-JS, GraphQL queries, formatting, i18n.
Quick example
function tag(strings, ...values) {
console.log(strings); // [ 'Hello, ', '!' ]
console.log(values); // [ 'Alex' ]
}
const name = 'Alex';
tag`Hello, ${name}!`;What happens under the hood:
// The interpreter does roughly this:
tag(['Hello, ', '!'], 'Alex');How it looks and what the tag receives
A plain template literal simply substitutes variables:
const name = 'Alex';
console.log(`Hello, ${name}!`);
// Hello, Alex!But if you put a function name in front of the template literal, JavaScript calls that function and passes it:
- The array of string parts (the text between substitutions).
- All the injected values from
${...}.
tag`Hello, ${name}!`That is a tagged template. So:
stringsis the array of all the text chunks between insertions;valuesis the array of all injected values, collected by the rest parameter.
There is always one more text part than there are values, because a template starts and ends with text, possibly empty.
Processing the content: your own templating
A tag function can be used to build your own template languages or to format strings.
function highlight(strings, ...values) {
return strings.reduce(
(result, str, i) => result + str + (values[i] ? `<b>${values[i]}</b>` : ''),
''
);
}
const name = 'Alex';
const lang = 'JavaScript';
const message = highlight`Hello, ${name}! You love ${lang}.`;
console.log(message);
// Hello, <b>Alex</b>! You love <b>JavaScript</b>.Here we caught every ${...} insertion and wrapped it in <b>...</b>.
A formatter is just as easy:
function format(strings, ...values) {
return strings.reduce(
(res, str, i) => res + str + (values[i] !== undefined ? `[${values[i]}]` : ''),
''
);
}
const a = 10, b = 20;
console.log(format`Sum of ${a} and ${b} is ${a + b}`);
// Sum of [10] and [20] is [30]XSS protection: safe HTML
The most useful scenario: the tag escapes everything that came from outside, so the markup stays safe.
function safeHTML(strings, ...values) {
const escape = str =>
String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
return strings.reduce(
(res, str, i) => res + str + (values[i] ? escape(values[i]) : ''),
''
);
}
const userInput = '<script>alert("Hacked!")</script>';
const html = safeHTML`<div>User says: ${userInput}</div>`;
console.log(html);
// <div>User says: <script>alert("Hacked!")</script></div>Thanks to the tag the HTML stays safe, with no foreign script execution. Crucially, only the values are escaped; the static template text written by the developer is left untouched.
Real world uses
-
Styled Components (React)
javascriptconst Button = styled.button` background: ${props => props.primary ? 'blue' : 'gray'}; color: white; `;Here
styled.buttonis a tag function that receives the template and builds CSS from it. -
GraphQL queries
javascriptconst query = gql` query GetUser { user(id: 1) { name } } `;The
gqltag parses the query text into an AST before it is ever sent to the server. -
Internationalisation (i18n)
javascripttranslate`Welcome, ${userName}!`;The tag looks the translation up by the static part and injects the values into it.
Comparison with a plain template literal
| Feature | Plain template literal | Tagged template literal |
|---|---|---|
| Syntax | `Hello, ${name}` | tag`Hello, ${name}` |
| Substitutions | Inserted automatically | Handled manually inside the tag |
| Flexibility | Interpolation only | Can filter, format, escape |
| Result | Always a string | Whatever the tag returns |
| Used for | Simple strings | Templating, security, CSS-in-JS, SQL, i18n |
Summary:
| Question | Answer |
|---|---|
| What it is | A template literal preceded by a tag function that receives the string parts and the values |
| Why it exists | To control how the ${} insertions are joined and interpreted |
| Where it is used | Safe HTML, CSS-in-JS, formatting, internationalisation |
| Key parameters | strings (template parts), values (injected values) |
Common mistakes
- Adding parentheses after the tag.
tag(Hello ${name})is an ordinary call with an already joined string; a tagged template is written without parentheses, straight after the function name. - Expecting the tag to receive a finished string. The first argument is the array of parts and the rest arrive through rest parameters; joining them is the function's job.
- Getting the counts wrong.
stringsalways has one element more thanvalues, so a naive loop overvaluesdrops the final chunk of text. - Escaping the wrong thing. Escape the values, not the whole result: running the static template HTML through the escaper turns the markup into plain text.
- Forgetting
strings.raw. The normal array holds processed escape sequences, whilestrings.rawgives the raw text; that is exactly whatString.rawis built on. - Assuming a tag must return a string. A tag can return an object, a function or an AST, which is what
gqland CSS-in-JS libraries do.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.