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

Entidades HTML:  , & e os caracteres especiais

Quando você precisa escrever &lt; em vez de <, por que &nbsp; não é um espaço comum e o que muda quando o arquivo já está salvo em UTF-8.

Rodolfo Mori12 min de leitura

Uma entidade HTML — chamada de referência de caractere na especificação — é uma sequência que representa um caractere dentro do código-fonte. &lt; produz o sinal de menor, &amp; produz & e &nbsp; produz um espaço inquebrável.

Pense numa mensagem em que alguns sinais já são comandos. Para escrever o sinal como conteúdo, você usa um código combinado: o parser vê &lt; e entrega o sinal de menor como texto, em vez de iniciar uma tag. No HTML, essa proteção é necessária para caracteres reservados em certos contextos; acentos, emoji e símbolos comuns podem ser escritos diretamente quando o documento está em UTF-8.

O ponto importante não é decorar uma tabela. É reconhecer quando o parser pode confundir conteúdo com sintaxe e quando &nbsp; muda o comportamento de quebra de linha, não apenas a aparência.

Os exemplos são da Disco Voador, uma loja de vinil: ficha do álbum, preço, busca com filtros e o selo que o cliente cola no próprio site. Toda saída abaixo veio de execução real no Node 24 com jsdom, que implementa o mesmo algoritmo de análise de HTML que os navegadores seguem.

Os dois caracteres que o HTML guarda para si

O analisador lê o arquivo procurando dois sinais. O < abre uma tag; o & abre uma entidade. Todo o resto é texto comum. São esses dois, então, que não podem aparecer crus quando você quer que sejam lidos como texto.

Na loja, o caso que aparece toda semana é o e-comercial no nome da dupla:

html
<h2>Bookends &mdash; Simon &amp; Garfunkel</h2>
<h2>Bookends &mdash; Simon & Garfunkel</h2>
js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<h2 id="certo">Bookends &mdash; Simon &amp; Garfunkel</h2>
<h2 id="errado">Bookends &mdash; Simon & Garfunkel</h2>`).window;

const certo = document.getElementById('certo');
const errado = document.getElementById('errado');

console.log('texto do certo :', certo.textContent);
console.log('texto do errado:', errado.textContent);
console.log('deu no mesmo?  :', certo.textContent === errado.textContent);
console.log('html de volta  :', errado.innerHTML);
texto do certo : Bookends — Simon & Garfunkel texto do errado: Bookends — Simon & Garfunkel deu no mesmo? : true html de volta : Bookends — Simon &amp; Garfunkel

As duas linhas deram no mesmo texto. O analisador é tolerante: quando o que vem depois do & não forma nome nenhum que ele conheça, ele desiste e devolve o e-comercial como texto. Repare na última linha — ao gerar o HTML de volta, o navegador reescreve &amp;. Ele sempre soube qual era a forma correta.

Tolerância não é permissão. A seção seguinte mostra onde ela acaba.

A busca da loja filtra por gênero, seção e loja física. São três parâmetros separados por &. O mesmo endereço aparece em dois lugares da página: no href do link e escrito por extenso, para o cliente copiar.

js
import { JSDOM } from 'jsdom';

const url = '/busca?genero=jazz&sect=vinil&reg=sp';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<a id="link" href="${url}">Jazz em vinil, loja de SP</a>
<code id="texto">${url}</code>`).window;

console.log('escrito         :', url);
console.log('dentro do href  :', document.getElementById('link').getAttribute('href'));
console.log('dentro do texto :', document.getElementById('texto').textContent);
escrito : /busca?genero=jazz&sect=vinil&reg=sp dentro do href : /busca?genero=jazz&sect=vinil&reg=sp dentro do texto : /busca?genero=jazz§=vinil®=sp

A mesma string, no mesmo arquivo. No atributo ela chegou inteira; no texto visível, &sect virou § e &reg virou ®. O cliente copia um endereço que não existe, e ninguém vê erro nenhum no console.

O atributo escapou por causa de uma regra de compatibilidade do padrão: dentro de um valor de atributo, um nome de entidade sem ponto e vírgula é deixado quieto quando o caractere seguinte é =, uma letra ou um número — justamente o desenho de um parâmetro de URL. Fora dessas hipóteses a proteção some. E o ponto e vírgula, que você aprendeu a sempre escrever, aqui joga contra: ele confirma a entidade e provoca a troca.

js
import { JSDOM } from 'jsdom';

const casos = [
  '/busca?genero=jazz&sect=vinil',
  '/busca?genero=jazz&sect2=vinil',
  '/busca?genero=jazz&sect;=vinil',
  '/busca?genero=jazz&sect&pagina=2',
  '/busca?genero=jazz&pagina=2&reg',
];

const html = casos.map((h, i) => `<a id="a${i}" href="${h}">x</a>`).join('');
const { document } = new JSDOM(`<!doctype html>${html}`).window;

for (const [i, escrito] of casos.entries()) {
  const lido = document.getElementById(`a${i}`).getAttribute('href');
  console.log(lido === escrito ? 'intacto' : 'QUEBROU', '|', lido);
}
intacto | /busca?genero=jazz&sect=vinil intacto | /busca?genero=jazz&sect2=vinil QUEBROU | /busca?genero=jazz§=vinil QUEBROU | /busca?genero=jazz§&pagina=2 QUEBROU | /busca?genero=jazz&pagina=2®

Três dos cinco quebraram. Os dois que passaram só passaram por acaso: o = depois de &sect, e o 2 de &sect2. Nenhum dos dois é uma decisão sua — é o nome do parâmetro que veio a calhar. Depender de qual caractere vem em seguida é uma aposta que você não precisa fazer: escreva &amp; entre os parâmetros e os cinco casos ficam intactos, sem exceção e sem regra para decorar.

A aspa dentro do atributo, que vira quatro atributos

O texto alternativo da capa cita o nome do álbum entre aspas. Quando essas aspas são iguais às que delimitam o atributo, o navegador fecha o atributo onde você não queria:

html
<img src="/capas/kind-of-blue.webp" alt="Capa de "Kind of Blue", edição japonesa">
<img src="/capas/kind-of-blue.webp" alt="Capa de &quot;Kind of Blue&quot;, edição japonesa">
js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<img id="quebrado" src="/capas/kind-of-blue.webp" alt="Capa de "Kind of Blue", edição japonesa">
<img id="certo" src="/capas/kind-of-blue.webp" alt="Capa de &quot;Kind of Blue&quot;, edição japonesa">`).window;

for (const id of ['quebrado', 'certo']) {
  const img = document.getElementById(id);
  console.log(id.toUpperCase());
  console.log('  alt      :', JSON.stringify(img.getAttribute('alt')));
  console.log('  atributos:', [...img.attributes].map((a) => a.name).join(', '));
}
QUEBRADO alt : "Capa de " atributos: id, src, alt, kind, of, blue",, edição, japonesa" CERTO alt : "Capa de \"Kind of Blue\", edição japonesa" atributos: id, src, alt

O primeiro img ficou com um alt cortado em oito caracteres e ganhou cinco atributos inventados: kind, of, blue",, edição e japonesa". Nenhum deles significa nada, e quem usa leitor de tela ouve “Capa de” e o silêncio. Ou você escreve &quot;, ou troca a aspa de fora por apóstrofo. O que colocar nesse texto está na lição de imagens em HTML.

Mostrar código HTML dentro de uma página HTML

A loja dá um selo para as lojas parceiras colarem no site delas. A página de ajuda precisa exibir esse trecho de HTML como texto. Sem escapar, o trecho não aparece — ele acontece:

html
<pre id="errado"><code><a href="https://discovoador.com.br">Selo Disco Voador</a></code></pre>
<pre id="certo"><code>&lt;a href="https://discovoador.com.br"&gt;Selo Disco Voador&lt;/a&gt;</code></pre>
js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<pre id="errado"><code><a href="https://discovoador.com.br">Selo Disco Voador</a></code></pre>
<pre id="certo"><code>&lt;a href="https://discovoador.com.br"&gt;Selo Disco Voador&lt;/a&gt;</code></pre>`).window;

for (const id of ['errado', 'certo']) {
  const bloco = document.getElementById(id);
  console.log(id.toUpperCase());
  console.log('  o cliente copia :', JSON.stringify(bloco.textContent));
  console.log('  tags dentro     :', [...bloco.querySelectorAll('*')].map((e) => e.tagName).join(', '));
}
ERRADO o cliente copia : "Selo Disco Voador" tags dentro : CODE, A CERTO o cliente copia : "<a href=\"https://discovoador.com.br\">Selo Disco Voador</a>" tags dentro : CODE

No primeiro caso o <a> virou um link de verdade dentro do <code>: o cliente seleciona, copia e leva só as palavras “Selo Disco Voador”. No segundo, &lt; e &gt; fizeram o analisador tratar tudo como texto, e o que ele copia é o código inteiro.

Essa mesma regra separa um site em pé de um site vandalizado. Se a resenha que um cliente escreveu for colada na página sem escapar, uma resenha com <script> dentro vira script executado. Por isso, ao escrever pela mão do JavaScript, textContent é o padrão e innerHTML é a exceção — a diferença entre os dois está na lição de innerHTML e textContent.

&nbsp; não é espaço: é o caractere 160

Espaço comum, espaço inquebrável e espaço estreito são idênticos na tela e diferentes na memória. A ficha de preço usa os três:

html
<span id="comum">R$ 249,90</span>
<span id="nbsp">R$&nbsp;249,90</span>
<span id="estreito">R$&#8239;249,90</span>
js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<span id="comum">R$ 249,90</span>
<span id="nbsp">R$&nbsp;249,90</span>
<span id="estreito">R$&#8239;249,90</span>`).window;

for (const id of ['comum', 'nbsp', 'estreito']) {
  const texto = document.getElementById(id).textContent;
  const codigo = texto.codePointAt(2);
  const hex = 'U+' + codigo.toString(16).toUpperCase().padStart(4, '0');
  console.log(id.padEnd(9), '| decimal', String(codigo).padStart(4), '|', hex, '| split(" ") devolve', texto.split(' ').length);
}
comum | decimal 32 | U+0020 | split(" ") devolve 2 nbsp | decimal 160 | U+00A0 | split(" ") devolve 1 estreito | decimal 8239 | U+202F | split(" ") devolve 1

O 160 é o &nbsp;, e ele serve para uma coisa só: impedir que o navegador separe duas palavras no fim da linha. R$ sozinho numa linha e 249,90 na seguinte fica ruim de ler; 33&nbsp;rpm, Lado&nbsp;B e Simon&nbsp;&amp;&nbsp;Garfunkel são os outros usos legítimos.

A última coluna é a que morde depois. O script da vitrine ordena por preço partindo o texto no espaço — e o espaço que ele procura não está lá:

js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<span class="preco">R$ 249,90</span>
<span class="preco">R$&nbsp;189,90</span>`).window;

function valorNumerico(elemento) {
  const [, numero] = elemento.textContent.split(' ');
  return Number(numero.replace(',', '.'));
}

const precos = document.querySelectorAll('.preco');
console.log('primeiro disco:', valorNumerico(precos[0]));
console.log('segundo disco :', valorNumerico(precos[1]));
primeiro disco: 249.9 file:///private/tmp/discovoador/preco.mjs:9 return Number(numero.replace(',', '.')); ^   TypeError: Cannot read properties of undefined (reading 'replace') at valorNumerico (file:///private/tmp/discovoador/preco.mjs:9:24) at file:///private/tmp/discovoador/preco.mjs:14:32 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)   Node.js v24.16.0

O primeiro preço saiu. O segundo estourou porque split(' ') devolveu um array com um elemento só e numero ficou undefined. Os dois preços apareciam iguais na tela; o defeito só nasce quando alguém clica em “ordenar por preço”. Se esse TypeError já te pegou, a página de Cannot read properties of undefined destrincha as outras causas dele.

Por que o navegador junta os seus espaços num só

Fora do caractere 160, o HTML é indiferente à sua formatação. Em qualquer elemento comum — parágrafo, título, item de lista —, uma sequência de espaços, tabulações e quebras de linha é desenhada como um espaço:

html
<p>Entrega     em     24h
   para toda a capital</p>
js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<p id="p">Entrega     em     24h
   para toda a capital</p>`).window;

const texto = document.getElementById('p').textContent;
const desenhado = texto.replace(/[ \t\n\r]+/g, ' ');

console.log('caracteres no DOM  :', texto.length);
console.log('texto cru          :', JSON.stringify(texto));
console.log('o que aparece      :', JSON.stringify(desenhado));
console.log('caracteres na tela :', desenhado.length);
caracteres no DOM : 45 texto cru : "Entrega em 24h\n para toda a capital" o que aparece : "Entrega em 24h para toda a capital" caracteres na tela : 34

Os 45 caracteres continuam no DOM. Quem descarta os 11 excedentes é a renderização, não o analisador — e isso importa na prática: um textContent lido por JavaScript devolve os 45, com os espaços todos, mesmo que a tela mostre 34. Medir texto pelo que você vê é como comparar dois preços pelo tamanho da etiqueta.

A exceção é o pre, aquele mesmo do selo lá em cima: dentro dele o navegador preserva espaço, tabulação e quebra de linha exatamente como estão no arquivo. Fora dele, quem decide é a propriedade white-space do CSS, e o valor padrão dela é juntar tudo.

O caractere 160 também não entra nessa conta, porque não conta como espaço em branco para efeito de junção — é por isso que &nbsp;&nbsp;&nbsp; funciona como recuo, e é por isso que essa gambiarra precisa virar padding no CSS antes de alguém abrir o seu HTML.

Acento e cedilha: com UTF-8 a entidade virou opcional

&ccedil;, &atilde; e &eacute; são herança de quando o arquivo não conseguia guardar esses caracteres. Hoje as duas escritas produzem exatamente a mesma string:

js
import { JSDOM } from 'jsdom';

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">
<p id="direto">Edição japonesa — gravado em São Paulo</p>
<p id="entidade">Edi&ccedil;&atilde;o japonesa &mdash; gravado em S&atilde;o Paulo</p>`).window;

const a = document.getElementById('direto').textContent;
const b = document.getElementById('entidade').textContent;
console.log('escrito direto  :', a);
console.log('com entidade    :', b);
console.log('deu no mesmo?   :', a === b);

const bytes = Buffer.from('Edição japonesa', 'utf8');
console.log('letras          :', 'Edição japonesa'.length);
console.log('bytes em utf-8  :', bytes.length);
console.log('lido como latin1:', bytes.toString('latin1'));
escrito direto : Edição japonesa — gravado em São Paulo com entidade : Edição japonesa — gravado em São Paulo deu no mesmo? : true letras : 15 bytes em utf-8 : 17 lido como latin1: Edição japonesa

Escreva o acento direto: é mais curto, é pesquisável no editor e é o que o tradutor automático entende. As três últimas linhas explicam o outro sintoma famoso: 15 letras ocupam 17 bytes, porque ç e ã gastam dois bytes cada, e quem lê esses bytes como latin1 mostra ç no lugar do ç. A cura não é voltar para as entidades — é a linha <meta charset="utf-8"> no começo do <head>, assunto da lição de meta tags no HTML e do erro character encoding not declared.

Símbolos que ainda valem a pena

Estes você não tem no teclado, e a entidade é mais fácil de digitar e de revisar do que o caractere colado de algum lugar. A ficha técnica do disco usa seis deles:

html
<ul>
  <li>&copy; 2026 Disco Voador</li>
  <li>Rotação: 33&frac13; rpm</li>
  <li>Capa: 31,4 &times; 31,4 cm</li>
  <li>Frete gr&aacute;tis &rarr; acima de R$ 199</li>
  <li>Guardar a 20 &deg;C</li>
  <li>Aguardando reposi&ccedil;&atilde;o&hellip;</li>
</ul>
<p id="vezes">&times; &#215; &#xD7; ×</p>

O último parágrafo escreve o mesmo sinal de multiplicação de quatro maneiras diferentes, de propósito. O script abaixo imprime a ficha e depois compara as quatro formas:

js
import { JSDOM } from 'jsdom';

const ficha = `<ul>
  <li>&copy; 2026 Disco Voador</li>
  <li>Rotação: 33&frac13; rpm</li>
  <li>Capa: 31,4 &times; 31,4 cm</li>
  <li>Frete gr&aacute;tis &rarr; acima de R$ 199</li>
  <li>Guardar a 20 &deg;C</li>
  <li>Aguardando reposi&ccedil;&atilde;o&hellip;</li>
</ul>
<p id="vezes">&times; &#215; &#xD7; ×</p>`;

const { document } = new JSDOM(`<!doctype html><meta charset="utf-8">${ficha}`).window;

for (const li of document.querySelectorAll('li')) console.log(li.textContent);

const formas = document.getElementById('vezes').textContent.split(' ');
console.log('as quatro formas dão o mesmo caractere?', new Set(formas).size === 1);
console.log('número desse caractere:', formas[0].codePointAt(0));
© 2026 Disco Voador Rotação: 33⅓ rpm Capa: 31,4 × 31,4 cm Frete grátis → acima de R$ 199 Guardar a 20 °C Aguardando reposição… as quatro formas dão o mesmo caractere? true número desse caractere: 215

As duas últimas linhas são o ponto: &times;, &#215;, &#xD7; e o × colado direto chegam ao DOM como um único caractere, o de número 215. Todo caractere tem um número, e o nome é só um atalho para ele.

forma como se escreve quando usar
nome &times; o símbolo tem nome e você lembra dele
decimal &#215; símbolo sem nome, com o número em mãos
hexadecimal &#xD7; copiando de tabela Unicode, que usa U+00D7
direto × o caractere já está no texto e o arquivo é UTF-8

A entidade escrita errado, que aparece como texto na tela

Não existe erro no console para entidade malformada. O sintoma é o próprio código aparecendo na página. Quatro tentativas quase iguais, e só duas delas chegam ao caractere 160:

html
<li>R$&NBSP;249,90</li>
<li>R$&nbsp249,90</li>
<li>R$&espaco;249,90</li>
<li>R$&nbsp;249,90</li>
js
import { JSDOM } from 'jsdom';

const tentativas = ['R$&NBSP;249,90', 'R$&nbsp249,90', 'R$&espaco;249,90', 'R$&nbsp;249,90'];
const html = tentativas.map((t, i) => `<li id="i${i}">${t}</li>`).join('');
const { document } = new JSDOM(`<!doctype html><meta charset="utf-8"><ul>${html}</ul>`).window;

for (const [i, escrito] of tentativas.entries()) {
  const texto = document.getElementById(`i${i}`).textContent;
  const codigos = [...texto].map((c) => c.codePointAt(0)).join(' ');
  console.log(escrito.padEnd(16), '| na tela:', JSON.stringify(texto).padEnd(18), '| códigos:', codigos);
}
R$&NBSP;249,90 | na tela: "R$&NBSP;249,90" | códigos: 82 36 38 78 66 83 80 59 50 52 57 44 57 48 R$&nbsp249,90 | na tela: "R$ 249,90" | códigos: 82 36 160 50 52 57 44 57 48 R$&espaco;249,90 | na tela: "R$&espaco;249,90" | códigos: 82 36 38 101 115 112 97 99 111 59 50 52 57 44 57 48 R$&nbsp;249,90 | na tela: "R$ 249,90" | códigos: 82 36 160 50 52 57 44 57 48

Três leituras dessa saída, e as três contrariam o que a maioria supõe:

  • Nome de entidade tem caixa. &NBSP; não é &nbsp;, e ficou na tela como texto. O 38 que aparece logo depois do R$ naquela linha de códigos é o e-comercial em pessoa, entregue como caractere comum.
  • O ponto e vírgula é opcional em alguns nomes antigos. &nbsp sem ponto e vírgula produziu o mesmo 160. Isso funciona por compatibilidade com páginas dos anos 90, não por estar certo. Escreva sempre o ;.
  • Nome inventado não avisa. &espaco; saiu inteiro na página. É assim que o problema chega ao cliente: um preço com texto estranho no meio e nenhuma linha vermelha no console.

A primeira leitura tem uma armadilha embutida. Existe um punhado de nomes antigos que o padrão registrou duas vezes, um em minúsculo e outro em maiúsculo — e quem for conferir a regra da caixa justamente com um deles conclui o contrário do que acabou de ler:

js
import { JSDOM } from 'jsdom';

const nomes = ['&copy;', '&COPY;', '&Copy;', '&times;', '&TIMES;', '&nbsp;', '&NBSP;'];
const html = nomes.map((n, i) => `<li id="n${i}">${n}</li>`).join('');
const { document } = new JSDOM(`<!doctype html><meta charset="utf-8"><ul>${html}</ul>`).window;

for (const [i, nome] of nomes.entries()) {
  const texto = document.getElementById(`n${i}`).textContent;
  console.log(nome.padEnd(8), '->', JSON.stringify(texto), texto === nome ? '(ficou como texto)' : '(virou caractere)');
}
&copy; -> "©" (virou caractere) &COPY; -> "©" (virou caractere) &Copy; -> "&Copy;" (ficou como texto) &times; -> "×" (virou caractere) &TIMES; -> "&TIMES;" (ficou como texto) &nbsp; -> " " (virou caractere) &NBSP; -> "&NBSP;" (ficou como texto)

&COPY; funciona e &TIMES; não. Isso não é incoerência do navegador, e também não derruba a leitura de cima: o que o padrão tem não é uma regra de maiúsculas e minúsculas, e sim uma lista fechada de nomes literais. Nessa lista, &copy; e &COPY; são duas entradas separadas, cadastradas assim desde o HTML dos anos 90, enquanto &times; só entrou em minúsculo. &Copy;, que não está lá em forma nenhuma, morre como qualquer nome inventado.

Por isso, quando aparecer &alguma-coisa; escrito na tela do seu site, a checagem é sempre a mesma, nessa ordem: a caixa das letras, o ponto e vírgula, e se aquele nome exato existe na lista de referências do padrão — que está linkada nas fontes desta página.

O que vem depois

Você já sabe escrever qualquer caractere dentro do HTML. O passo seguinte é dar forma a esse texto — negrito, ênfase, citação e código — na lição de formatar texto em HTML. E se quiser ver onde esta lição encaixa no caminho inteiro, o guia de HTML mostra a ordem de estudo do começo ao fim.

Faça uma página com três linhas: uma expressão escrita como 3 &lt; 5, um link cuja query tem dois parâmetros e um preço R$ 249,90 que não pode quebrar entre moeda e valor. Use &lt;, &amp; no atributo e &nbsp; apenas no último caso. Inspecione textContent, innerHTML e o href final para confirmar o caractere exibido e a sintaxe preservada.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • html
  • entidades
  • nbsp
  • caracteres especiais
  • utf-8

Perguntas frequentes

Preciso escrever &gt; ou posso deixar o sinal de maior solto no texto?
No meio de um parágrafo o navegador aceita o sinal de maior solto, porque ele só tem significado depois de um sinal de menor. Ainda assim, a entidade custa quatro letras e mantém o trecho seguro caso ele um dia seja gerado por script ou colado dentro de um atributo.
Qual a diferença entre &#160; e &#xA0;?
Nenhuma no resultado. A primeira escreve o número do caractere em decimal e a segunda em hexadecimal, e as duas apontam para o mesmo espaço inquebrável. Use a forma que você lê sem consultar tabela.
Posso usar &nbsp; para afastar dois elementos na página?
Não. Espaçamento é trabalho do CSS, com margin, padding ou gap. O espaço inquebrável existe para impedir que duas palavras que precisam andar juntas caiam em linhas diferentes, como o R$ e o valor.
Emoji e símbolo de moeda precisam de entidade?
Não, desde que o arquivo esteja salvo em UTF-8 e a página declare esse charset. A entidade numérica só volta a ser útil quando o texto passa por um sistema antigo que não preserva o caractere original.

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 + jsdom 30.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Entidade HTML — developer.mozilla.org
  2. HTML Standard — Named character references — html.spec.whatwg.org

Continue por aqui