DEV Community

Cover image for Template Literals in JavaScript — From Messy Concatenation to Clean, Modern Strings
Janmejai Singh
Janmejai Singh

Posted on

Template Literals in JavaScript — From Messy Concatenation to Clean, Modern Strings

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);
Enter fullscreen mode Exit fullscreen mode

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 (\n inside 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!`;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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`;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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" : ""}.`

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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>`;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}" />`;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Nested backticks need escaping

// ❌ Syntax error
const str = `Click the `Run` button`;

// ✅ Escaped backtick
const str = `Click the \`Run\` button`;
Enter fullscreen mode Exit fullscreen mode

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>`);
Enter fullscreen mode Exit fullscreen mode

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`;
Enter fullscreen mode Exit fullscreen mode

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)