Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Template literals: interpolação de string em JS

Crase, ${} e string multilinha sem barra-n: como montar texto e HTML em JavaScript sem concatenar, com a saída real de cada exemplo rodada no Node 24.

Rodolfo Mori7 min de leitura

Template literal é a string escrita entre crases em vez de aspas. Dentro dela, ${} insere o valor de qualquer expressão, e a quebra de linha que você digita é a quebra de linha que sai. São dois recursos que aposentam a concatenação com + em praticamente todo código novo.

A troca é direta: onde você escrevia 'Olá, ' + cliente + '!', agora escreve a mesma frase entre crases, com ${cliente} no lugar do nome. Menos aspas para fechar, menos espaços perdidos, menos + no meio do caminho.

Pense num texto com campos para preencher

Imagine uma mensagem pronta — “Olá, ___, seu pedido ___ foi enviado” — com dois espaços reservados. Template literal é essa mensagem no código. A crase delimita o modelo e cada ${expressao} é um campo preenchido no momento em que a linha roda. O nome técnico dessa entrada é interpolação: colocar o resultado de uma expressão dentro do texto.

Ao contrário de um formulário em papel, o campo não aceita só um nome. Dentro de ${} cabe uma conta, uma chamada de função ou uma condição. Antes de seguir, tente transformar `Total: R$ ${preco * quantidade}` em concatenação com +. Conte quantas vezes você precisa abrir e fechar texto. Essa pequena comparação mostra por que a crase reduz erro sem inventar uma nova forma de calcular: a expressão continua sendo JavaScript normal.

Crase e ${}, lado a lado com o +

js
const cliente = 'Ana';
const pedido = 1042;
const total = 249.9;

const antes = 'Olá, ' + cliente + '! O pedido ' + pedido + ' saiu por R$ ' + total.toFixed(2) + '.';
const agora = `Olá, ${cliente}! O pedido ${pedido} saiu por R$ ${total.toFixed(2)}.`;

console.log(antes);
console.log(agora);
console.log(antes === agora);
Olá, Ana! O pedido 1042 saiu por R$ 249.90. Olá, Ana! O pedido 1042 saiu por R$ 249.90. true

As duas produzem exatamente a mesma string — a terceira linha prova. O que muda é quanta atenção cada versão exige de quem lê: na primeira, você precisa conferir manualmente cada espaço antes das aspas de fechamento.

Dentro do ${} cabe expressão, não só variável

Este é o ponto que a maioria dos tutoriais não menciona: ${} aceita qualquer expressão JavaScript, e o resultado dela é convertido para string.

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9, qtd: 1 },
  { nome: 'Mouse sem fio', preco: 149.9, qtd: 2 },
];

const total = itens.reduce((soma, i) => soma + i.preco * i.qtd, 0);

console.log(`${itens.length} ${itens.length === 1 ? 'item' : 'itens'} no carrinho`);
console.log(`Total: R$ ${total.toFixed(2)}`);
console.log(`Frete: ${total >= 199 ? 'grátis' : 'R$ 24,90'}`);
console.log(`Primeiro item: ${itens[0].nome.toUpperCase()}`);
2 itens no carrinho Total: R$ 589.70 Frete: grátis Primeiro item: TECLADO MECÂNICO

Chamada de método, operador ternário, acesso a índice, conta aritmética: tudo vale. O que não cabe é uma declaração — if, for e const não são expressões e não entram ali dentro. Quando a lógica passa de um ternário, tire-a do template e coloque numa variável ou numa função acima.

Multilinha sem \n

Numa string com aspas, a quebra de linha precisa ser escrita como \n. Numa template literal, você simplesmente aperta Enter:

js
const cliente = 'Ana';
const pedido = 1042;

const email = `Olá, ${cliente}.

Seu pedido ${pedido} foi confirmado e sai hoje do centro de distribuição.
Você recebe o código de rastreio assim que a transportadora coletar.

Equipe Club Store`;

console.log(email);
console.log('---');
console.log('linhas:', email.split('\n').length);
Olá, Ana.

Seu pedido 1042 foi confirmado e sai hoje do centro de distribuição. Você recebe o código de rastreio assim que a transportadora coletar.

Equipe Club Store

linhas: 6

O texto no código tem a mesma forma do texto na saída. Isso vale ouro em corpo de e-mail, em mensagem de WhatsApp e em qualquer template de notificação: o que você vê no editor é o que o cliente vê.

Montar HTML sem concatenar

Aqui a diferença deixa de ser conforto e vira legibilidade real. Com +, cada atributo HTML exige trocar de tipo de aspas e contar caracteres:

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9 },
  { nome: 'Mouse sem fio', preco: 149.9 },
];

const html = `<ul class="carrinho">
${itens
  .map((item) => `  <li><span>${item.nome}</span><b>R$ ${item.preco.toFixed(2)}</b></li>`)
  .join('\n')}
</ul>`;

console.log(html);
<ul class="carrinho"> <li><span>Teclado mecânico</span><b>R$ 289.90</b></li> <li><span>Mouse sem fio</span><b>R$ 149.90</b></li> </ul>

Repare que as aspas duplas do atributo class convivem tranquilamente com a crase — não há conflito de delimitador. E o ${} de dentro do map é outro template literal, aninhado dentro do primeiro: isso é permitido e é o padrão para gerar lista.

Aninhamento e interpolação de valores que não são string

js
const pedido = {
  id: 1042,
  cliente: 'Ana',
  itens: [
    { nome: 'Teclado mecânico', qtd: 1 },
    { nome: 'Mouse sem fio', qtd: 2 },
  ],
};

const resumo = `Pedido ${pedido.id} — ${pedido.cliente}
${pedido.itens.map((i) => `  ${i.qtd}x ${i.nome}${i.qtd > 1 ? ' (múltiplos)' : ''}`).join('\n')}`;

console.log(resumo);
Pedido 1042 — Ana 1x Teclado mecânico 2x Mouse sem fio (múltiplos)

Todo valor interpolado passa por uma conversão para string — e nem sempre o resultado é o que você quer:

js
const pedido = { id: 1042, cliente: 'Ana' };
const itens = ['Teclado', 'Mouse'];

console.log(`pedido: ${pedido}`);
console.log(`itens: ${itens}`);
console.log(`pedido: ${JSON.stringify(pedido)}`);
console.log(`nulo: ${null} / indefinido: ${undefined}`);
pedido: [object Object] itens: Teclado,Mouse pedido: {"id":1042,"cliente":"Ana"} nulo: null / indefinido: undefined

[object Object] num log ou numa tela é sempre isto: um objeto interpolado direto. Use JSON.stringify quando quiser ver o conteúdo. Repare também que null e undefined viram o texto "null" e "undefined" — se o campo pode estar vazio, trate antes de interpolar.

Escapar crase, ${ e barra invertida

js
const campo = 'quantidade';

console.log(`Use \`${campo}\` no corpo do POST.`);
console.log(`O literal \${${campo}} não é interpolado.`);
console.log(`Quebra manual:\nsegunda linha`);
console.log(String.raw`caminho: C:\pedidos\nota.pdf`);
Use `quantidade` no corpo do POST. O literal ${quantidade} não é interpolado. Quebra manual: segunda linha caminho: C:\pedidos\nota.pdf

Barra invertida antes da crase escreve a crase; \${ escreve o ${ literal. E String.raw desliga todos os escapes de uma vez, o que salva caminho do Windows e expressão regular escrita à mão — sem ele, o \n de \nota.pdf viraria uma quebra de linha.

Tagged template: a função colada na crase

Você vai encontrar isso em bibliotecas de estilo e de tradução. Uma função imediatamente antes da crase recebe as partes fixas e os valores separadamente:

js
const real = new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' });

function moeda(partes, ...valores) {
  return partes.reduce((texto, parte, i) => {
    const valor = valores[i];
    const formatado = typeof valor === 'number' ? real.format(valor) : (valor ?? '');
    return texto + parte + formatado;
  }, '');
}

const cliente = 'Ana';
const total = 249.9;
const frete = 0;

console.log(moeda`${cliente} pagou ${total} com frete ${frete}.`);
Ana pagou R$ 249,90 com frete R$ 0,00.

A tag moeda formatou todo número como real brasileiro e deixou o texto passar intacto — inclusive o frete zero, que um formatador ingênuo teria escondido. Você não precisa escrever tags no primeiro mês, mas precisa reconhecer uma quando aparecer no código de outra pessoa.

Erros comuns

Aspas no lugar da crase: a interpolação vira texto

js
const cliente = 'Ana';
const total = 249.9;

console.log('Olá, ${cliente}! Total: R$ ${total}');
console.log("Olá, ${cliente}! Total: R$ ${total}");
console.log(`Olá, ${cliente}! Total: R$ ${total}`);
Olá, ${cliente}! Total: R$ ${total} Olá, ${cliente}! Total: R$ ${total} Olá, Ana! Total: R$ 249.9

Duas das três linhas imprimiram o ${cliente} cru. Não houve erro, aviso ou sublinhado vermelho: ${} só tem significado dentro de crases, e dentro de aspas ele é texto comum. Quando um e-mail sai para o cliente com ${nome} no corpo, foi isto que aconteceu. O jeito rápido de conferir é olhar a cor da string no editor — o realce muda entre crase e aspas.

O erro que lança: esquecer o + na concatenação antiga

O motivo pelo qual as template literals existem:

js
const cliente = 'Ana';

const saudacao = 'Olá, ' cliente '! Seu pedido saiu.';

console.log(saudacao);
file:///private/tmp/club-store/esqueceu-o-mais.mjs:3 const saudacao = 'Olá, ' cliente '! Seu pedido saiu.'; ^^^^^^^

SyntaxError: Unexpected identifier ‘cliente’ at compileSourceTextModule (node:internal/modules/esm/utils:318:16) at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:90:18) at #translate (node:internal/modules/esm/loader:435:20) at afterLoad (node:internal/modules/esm/loader:491:29) at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:496:12) at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:549:36) at afterResolve (node:internal/modules/esm/loader:597:52) at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:603:12) at node:internal/modules/esm/loader:622:32 at TracingChannel.tracePromise (node:diagnostics_channel:362:14)

Node.js v24.16.0

O motor leu uma string, depois um identificador solto, e não soube o que fazer entre os dois — daí Unexpected identifier 'cliente'. Nada rodou: erro de sintaxe acontece na compilação, antes da primeira linha executar. Esse erro simplesmente não existe na versão com crase, porque não há + para esquecer.

Quando usar cada forma

situação forma recomendada
texto fixo, sem variável aspas simples
uma ou mais variáveis no meio template literal
várias linhas template literal
HTML ou SQL montado em código template literal
caminho de arquivo ou regex com \ String.raw
juntar array de textos .join(), não + num laço

A última linha vale um alerta: concatenar dentro de um laço com texto += item funciona, mas array.join('\n') diz melhor a intenção e evita a variável acumuladora. Os métodos que operam sobre esse texto — trim, replace, padStart, split — e a razão pela qual nenhum deles altera a string original são o assunto de strings em JavaScript, a próxima parada da trilha. Se quiser rever como cada variável interpolada foi declarada, a lição de variáveis cobre isso, e o guia de JavaScript tem o mapa completo — todos os assuntos também estão listados na categoria front-end.

  • template literals
  • strings
  • interpolacao
  • html
  • crase

Perguntas frequentes

Onde fica a crase no teclado brasileiro?
No ABNT2, ela está na tecla de acento agudo, à direita do Ç, pressionada junto com Shift — e ela é um acento morto, então você precisa apertar espaço em seguida ou digitar duas vezes para ela aparecer sozinha. É a razão número um de gente desistir de template literal no primeiro dia.
Template literal é mais lento que concatenação?
Não em nenhuma medida que importe. Os motores modernos compilam as duas formas para praticamente o mesmo código. Escolha por legibilidade: se a string tem mais de uma variável no meio, o template literal ganha de longe.
Posso usar template literal para montar HTML de verdade?
Para montar, sim, e é o uso mais comum. Só não jogue direto em innerHTML com conteúdo vindo do usuário — isso abre a porta para injeção de script. Para texto de origem externa, use textContent ou escape os caracteres antes de interpolar.
O que é aquela função grudada antes da crase?
É um tagged template. A função recebe as partes fixas do texto num array e os valores interpolados como argumentos separados, e decide o que fazer com cada um. É como bibliotecas de CSS-in-JS e de tradução funcionam por dentro.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Template literals (Strings de modelo) — developer.mozilla.org
  2. MDN — String.raw() — developer.mozilla.org
  3. ECMAScript 2026 Language Specification — Template Literals — tc39.es

Continue por aqui