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.
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 +
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);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.
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()}`);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:
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);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:
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);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
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);Todo valor interpolado passa por uma conversão para string — e nem sempre o resultado é o que você quer:
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}`);[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
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`);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:
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}.`);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
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}`);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:
const cliente = 'Ana';
const saudacao = 'Olá, ' cliente '! Seu pedido saiu.';
console.log(saudacao);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.
Perguntas frequentes
Onde fica a crase no teclado brasileiro?
Template literal é mais lento que concatenação?
Posso usar template literal para montar HTML de verdade?
O que é aquela função grudada antes da crase?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- MDN — Template literals (Strings de modelo) — developer.mozilla.org
- MDN — String.raw() — developer.mozilla.org
- ECMAScript 2026 Language Specification — Template Literals — tc39.es


