Skip to main content

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 as tag(['Hello, ', '!'], name).
  • A tag may return anything, not necessarily a string.
  • Main uses: safe HTML, CSS-in-JS, GraphQL queries, formatting, i18n.

Quick example

javascript
function tag(strings, ...values) { console.log(strings); // [ 'Hello, ', '!' ] console.log(values); // [ 'Alex' ] } const name = 'Alex'; tag`Hello, ${name}!`;

What happens under the hood:

javascript
// The interpreter does roughly this: tag(['Hello, ', '!'], 'Alex');

How it looks and what the tag receives

A plain template literal simply substitutes variables:

javascript
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:

  1. The array of string parts (the text between substitutions).
  2. All the injected values from ${...}.
javascript
tag`Hello, ${name}!`

That is a tagged template. So:

  • strings is the array of all the text chunks between insertions;
  • values is 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.

javascript
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:

javascript
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.

javascript
function safeHTML(strings, ...values) { const escape = str => String(str) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;'); 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: &lt;script&gt;alert("Hacked!")&lt;/script&gt;</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

  1. Styled Components (React)

    javascript
    const Button = styled.button` background: ${props => props.primary ? 'blue' : 'gray'}; color: white; `;

    Here styled.button is a tag function that receives the template and builds CSS from it.

  2. GraphQL queries

    javascript
    const query = gql` query GetUser { user(id: 1) { name } } `;

    The gql tag parses the query text into an AST before it is ever sent to the server.

  3. Internationalisation (i18n)

    javascript
    translate`Welcome, ${userName}!`;

    The tag looks the translation up by the static part and injects the values into it.

Comparison with a plain template literal

FeaturePlain template literalTagged template literal
Syntax`Hello, ${name}`tag`Hello, ${name}`
SubstitutionsInserted automaticallyHandled manually inside the tag
FlexibilityInterpolation onlyCan filter, format, escape
ResultAlways a stringWhatever the tag returns
Used forSimple stringsTemplating, security, CSS-in-JS, SQL, i18n

Summary:

QuestionAnswer
What it isA template literal preceded by a tag function that receives the string parts and the values
Why it existsTo control how the ${} insertions are joined and interpreted
Where it is usedSafe HTML, CSS-in-JS, formatting, internationalisation
Key parametersstrings (template parts), values (injected values)

Common mistakes

  1. 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.
  2. 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.
  3. Getting the counts wrong. strings always has one element more than values, so a naive loop over values drops the final chunk of text.
  4. 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.
  5. Forgetting strings.raw. The normal array holds processed escape sequences, while strings.raw gives the raw text; that is exactly what String.raw is built on.
  6. Assuming a tag must return a string. A tag can return an object, a function or an AST, which is what gql and CSS-in-JS libraries do.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.