Template Literals in JavaScript — From Messy Concatenation to Clean, Modern Strings
JavaScript strings used to be a chore. Between escaped quotes, awkward \n newlines, and chains of + operators, writing dynamic strings felt like assembling furniture with the wrong tools.
ES6 template literals changed all of that in 2015. A decade later, they're still one of the most impactful readability improvements in modern JavaScript — and they're still underused by developers who haven't fully made the switch.
This guide covers everything: the problem, the solution, the syntax, real use cases, and the advanced tricks most tutorials skip.
The Problem with String Concatenation
Let's start honest. Here's what string-building looked like before template literals:
const firstName = "Ananya";
const lastName = "Sharma";
const score = 98;
const subject = "JavaScript";
// 😩 Traditional approach
const report = "Student: " + firstName + " " + lastName +
"\nSubject: " + subject +
"\nScore: " + score + "/100" +
"\nStatus: " + (score >= 60 ? "Pass" : "Fail");
console.log(report);
This works. But it's:
- Hard to read (you have to mentally track
+and"pairs) - Easy to mess up (a missing space inside quotes? typo city)
- Painful to maintain (adding a new line means touching the whole chain)
- Ugly for multi-line output (
\ninside strings is visual noise)
The more complex your string, the worse this gets.
The Solution: Template Literal Syntax
Template literals use backticks (`) instead of quotes.
// That's it. This is a template literal.
const greeting = `Hello, World!`;
But the real power comes from two features packed inside: string interpolation and native multi-line support.
String Interpolation: ${} Is Your New Best Friend
Embed any variable or expression using ${expression} inside the backticks:
const firstName = "Ananya";
const lastName = "Sharma";
const score = 98;
const subject = "JavaScript";
// ✅ Template literal approach
const report = `Student: ${firstName} ${lastName}
Subject: ${subject}
Score: ${score}/100
Status: ${score >= 60 ? "Pass" : "Fail"}`;
console.log(report);
// Student: Ananya Sharma
// Subject: JavaScript
// Score: 98/100
// Status: Pass
Same output. Completely different experience writing and reading it.
The ${} Placeholder Accepts Any Expression
const a = 10;
const b = 5;
console.log(`Sum: ${a + b}`); // Sum: 15
console.log(`Power: ${a ** b}`); // Power: 100000
console.log(`Max: ${Math.max(a, b)}`); // Max: 10
// Functions inline
const shout = str => str.toUpperCase();
console.log(`Shout: ${shout("hello")}`); // Shout: HELLO
// Arrays
const fruits = ["Mango", "Papaya", "Guava"];
console.log(`Fruits: ${fruits.join(", ")}`); // Fruits: Mango, Papaya, Guava
// Objects
const user = { name: "Rahul", tier: "Pro" };
console.log(`User ${user.name} is on the ${user.tier} plan.`);
// User Rahul is on the Pro plan.
Multi-line Strings: Just Press Enter
No more \n. No more string concatenation across lines. With template literals, line breaks are literal:
// ❌ Old approach
const oldEmail = "Dear Vikram,\n\n" +
"Thank you for joining Web Dev Cohort 2026.\n" +
"Your first session starts on March 16.\n\n" +
"Best regards,\nThe Team";
// ✅ New approach
const email = `Dear Vikram,
Thank you for joining Web Dev Cohort 2026.
Your first session starts on March 16.
Best regards,
The Team`;
The template literal version looks exactly like the output — no translation required.
Building HTML Fragments
This is where multi-line template literals really shine:
const product = {
name: "Mechanical Keyboard",
price: 3499,
inStock: true,
rating: 4.7
};
// ✅ Clean HTML template
const card = `
<div class="product-card">
<h3>${product.name}</h3>
<p class="price">₹${product.price.toLocaleString("en-IN")}</p>
<span class="badge ${product.inStock ? "in-stock" : "out-of-stock"}">
${product.inStock ? "In Stock" : "Out of Stock"}
</span>
<p>⭐ ${product.rating}/5</p>
</div>
`.trim();
document.querySelector("#app").innerHTML = card;
Before vs After: The Visual Comparison
Here's a side-by-side that shows just how dramatic the improvement is:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BEFORE: String Concatenation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"Welcome, " + user + "! You have " + count
+ " message" + (count !== 1 ? "s" : "") + "."
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AFTER: Template Literal
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`Welcome, ${user}! You have ${count}
message${count !== 1 ? "s" : ""}.`
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Same logic. The template literal version is scannable at a glance.
Nested Template Literals
Template literals can be nested — useful for generating lists from arrays:
const students = ["Arjun", "Priya", "Karan"];
const html = `
<ul class="student-list">
${students.map(name => `<li>${name}</li>`).join("\n ")}
</ul>
`;
console.log(html);
// <ul class="student-list">
// <li>Arjun</li>
// <li>Priya</li>
// <li>Karan</li>
// </ul>
Tagged Templates: The Advanced Feature
Tagged templates let you intercept template literal processing with a function. The tag function receives the static string parts and the interpolated values separately.
// Syntax: tagFunction`string ${value} more string`
function currencyINR(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i - 1];
const formatted = typeof value === "number"
? `₹${value.toLocaleString("en-IN")}`
: value;
return result + (formatted ?? "") + str;
});
}
const price = 149999;
const item = "MacBook Pro";
console.log(currencyINR`The ${item} costs ${price} after GST.`);
// The MacBook Pro costs ₹1,49,999 after GST.
Where Tagged Templates Are Used in the Wild
// styled-components — CSS-in-JS
const Button = styled.button`
background: ${props => props.primary ? "#0070f3" : "white"};
color: ${props => props.primary ? "white" : "#0070f3"};
padding: 0.5rem 1.25rem;
border-radius: 6px;
`;
// GraphQL (Apollo / urql)
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
`;
// Lit HTML
const template = html`<p>Hello, ${name}!</p>`;
Tagged templates are one of the most powerful and underrated features in modern JavaScript.
String.raw — A Built-in Tag
JavaScript ships with String.raw, a built-in tag that prevents escape sequence processing:
// Regular template literal
console.log(`C:\new\folder`);
// C:
// ew
// older ← \n and \f were interpreted!
// String.raw — preserves backslashes
console.log(String.raw`C:\new\folder`);
// C:\new\folder ← exactly as written
Handy for Windows file paths, regex patterns, and LaTeX strings.
Real-World Use Cases Cheat Sheet
// 1. API endpoint construction
const getUser = (id) => `${API_BASE}/users/${id}/profile`;
const search = (q, page) => `${API_BASE}/search?q=${encodeURIComponent(q)}&page=${page}`;
// 2. Dynamic class names (React / Vanilla JS)
const btnClass = `btn ${isLoading ? "btn--loading" : ""} ${variant}`.trim();
// 3. Structured log messages
const log = (level, msg) =>
`[${new Date().toISOString()}] [${level}] ${msg}`;
// 4. Error messages with context
throw new Error(`Expected type "string" but got "${typeof value}" at key "${key}".`);
// 5. SQL query building (static parts only — always parameterize user input!)
const query = `
SELECT id, name, email
FROM users
WHERE role = 'admin'
ORDER BY created_at DESC
LIMIT ${pageSize} OFFSET ${page * pageSize}
`;
// 6. SVG generation
const circle = (cx, cy, r, color) =>
`<circle cx="${cx}" cy="${cy}" r="${r}" fill="${color}" />`;
Common Pitfalls to Avoid
1. Unintended leading/trailing whitespace
const msg = `
Hello
`;
// msg starts with \n and has trailing \n
// Use .trim() if you need to remove them
2. Nested backticks need escaping
// ❌ Syntax error
const str = `Click the `Run` button`;
// ✅ Escaped backtick
const str = `Click the \`Run\` button`;
3. XSS risk with innerHTML
// ⚠️ Don't do this with untrusted user input
element.innerHTML = `<p>${userInput}</p>`;
// ✅ Use textContent for plain text
element.textContent = userInput;
// ✅ Or sanitize with DOMPurify for HTML
element.innerHTML = DOMPurify.sanitize(`<p>${userInput}</p>`);
Feature Comparison Table
| Feature | Old Concatenation | Template Literals |
|---|---|---|
| Syntax | "" + var + "" |
`${var}` |
| Readability | ❌ Cluttered | ✅ Clean |
| Multi-line | ❌ \n hacks |
✅ Natural |
| Expressions | ❌ Awkward +
|
✅ Inside ${}
|
| Nested structures | ❌ Very messy | ✅ Composable |
| Advanced processing | ❌ Not possible | ✅ Tagged templates |
| Raw strings | ❌ Manual escape | ✅ String.raw
|
| Performance | Same | Same |
| Browser support | All | ES6+ / all modern |
TL;DR — Everything in One Block
// ── Basic ─────────────────────────────────────
const name = "Dev";
const msg = `Hello, ${name}!`;
// ── Expression ────────────────────────────────
const tax = `Total: ₹${(price * 1.18).toFixed(2)}`;
// ── Multi-line ────────────────────────────────
const html = `
<h1>${title}</h1>
<p>${body}</p>
`;
// ── Ternary ───────────────────────────────────
const status = `${online ? "🟢 Online" : "🔴 Offline"}`;
// ── Nested ────────────────────────────────────
const list = `<ul>${items.map(i => `<li>${i}</li>`).join("")}</ul>`;
// ── Tagged ────────────────────────────────────
const safe = sanitize`User said: ${input}`;
// ── Raw ───────────────────────────────────────
const path = String.raw`C:\Users\Dev\Documents`;
Conclusion
Template literals aren't a gimmick — they solve a genuine pain point in JavaScript. Every time you use them, you're writing code that's cleaner, safer, and easier for others (including future you) to understand.
The switch from + concatenation is almost zero effort, and the payoff is immediate. If you're building anything with strings — and you always are — template literals belong in your default toolkit.
Found this helpful? Drop a ❤️, save it for reference, and share it with someone who's still typing "Hello " + name + "!".
Part of the Web Dev Cohort 2026 series — new JavaScript fundamentals every week.
Discussion
- What's the most creative use of tagged templates you've seen in production?
- Do you use template literals by default now, or do you still reach for quotes sometimes?
Let's talk in the comments 👇
Top comments (0)