Tagged Template Literals: The Feature Behind Every html`` You've Seen
Template literals everyone knows. But put a bare function name in front of the backtick and you've entered a different feature entirely:
function tag(strings, ...values) {
console.log(strings); // ["Hello ", ", you have ", " messages"]
console.log(values); // ["Anna", 5]
}
tag`Hello ${name}, you have ${count} messages`;
The function receives the static string parts and the interpolated values separately, before they're joined. That separation is the whole superpower: the tag decides how values become text.
The practical payoff: an auto-escaping html tag
Building HTML with plain template literals is an XSS bug waiting for user input. A tag function fixes it structurally - static parts pass through, dynamic parts get escaped, always:
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&').replaceAll('<', '<')
.replaceAll('>', '>').replaceAll('"', '"')
.replaceAll("'", ''');
}
function html(strings, ...values) {
return strings.reduce((out, str, i) =>
out + str + (i < values.length ? escapeHtml(values[i]) : ''), '');
}
const card = html`
${user.name}: ${user.message}
`;
Now <script> in a comment renders as harmless text, and nobody on the team has to remember to escape anything. The safe path became the only path - which is what good security code looks like.
You can extend it with an opt-out for pre-sanitized fragments (a raw() wrapper object the tag recognizes), which mirrors how the big template systems do it.
The raw strings bonus
The strings array carries a .raw property with backslashes unprocessed, and String.raw is a built-in tag exposing it. It's the sane way to write Windows paths and regex sources:
String.raw`C:\Users\node_modules`; // backslashes survive
new RegExp(String.raw`\d+\.\d{2}`); // no double-escaping
Where you've already met this feature
lit-html's rendering, styled-components' CSS blocks, many SQL clients' safe query builders (values become bound parameters, not concatenated strings) - all tagged templates. The pattern generalizes: whenever "static template + untrusted values" appear together, a tag function can enforce the safety rule at the syntax level.
Server-side folks: this is the same philosophy as prepared statements, and the same reason security-sensitive comparisons get dedicated functions. Make the correct thing the effortless thing.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.