O que é HTML e como o navegador transforma tag em página
HTML é marcação, não programação: o que cada tag faz, o que o navegador faz com ela e por que um arquivo .html abre sem instalar nada.
HTML é uma linguagem de marcação: ela não manda o computador fazer nada, ela diz o que cada pedaço de texto é — isto é um título, isto é um parágrafo, isto é um link. O navegador lê essa descrição, monta uma árvore de elementos na memória e só então pinta a tela.
Os exemplos deste artigo montam a página da Livraria Página Sete, uma livraria de bairro que ainda não tem site. O ponto de partida é o aviso que hoje está impresso e colado na porta dela.
Etiquetas numa mudança: por que marcação não é programação
Pense nas caixas de uma mudança. Escrever “cozinha”, “livros” e “frágil” não move nenhuma caixa, mas dá significado ao conteúdo e permite que cada pessoa saiba como tratar aquilo. O HTML faz esse trabalho na página: suas tags dizem “título”, “parágrafo”, “link” e “formulário”.
Voltando ao termo correto, HTML é uma linguagem de marcação. As tags
descrevem estrutura e semântica; elas não criam condições nem executam laços.
Na comparação entre o aviso .txt e o .html, conte quantos elementos o parser
encontra em cada um. Essa saída é a prova de que marcar não é apenas mudar a
aparência: é produzir uma estrutura que o navegador entende.
A sigla diz exatamente isso: HTML é HyperText Markup Language, linguagem de marcação de hipertexto. “Hipertexto” é o texto que aponta para outro texto — o link, a invenção que fez a web existir. “Marcação” é todo o resto do trabalho: dizer o que cada trecho de texto é antes de ele aparecer na tela.
Repare no que o HTML não tem: não tem if, não tem repetição, não tem
variável, não tem conta. Nada dentro de um arquivo .html decide alguma coisa.
Por isso “linguagem de marcação” não é um jeito educado de dizer “linguagem de
programação fácil” — são categorias diferentes de ferramenta.
Uma comparação que ajuda: programar é escrever a receita; marcar é passar marca-texto amarelo no ingrediente e verde no modo de preparo. O texto continua o mesmo. O que muda é que agora alguém consegue separar as partes.
Este é o aviso da livraria, salvo como texto puro:
Livraria Página Sete
Rua das Acácias, 120 — aberto de terça a domingo
Clube de leitura
Toda quinta, às 19h, com café por conta da casa.E este é o mesmo aviso, marcado:
<h1>Livraria Página Sete</h1>
<p>Rua das Acácias, 120 — aberto de terça a domingo</p>
<h2>Clube de leitura</h2>
<p>Toda quinta, às 19h, com café por conta da casa.</p>Para uma pessoa lendo, os dois dizem exatamente a mesma coisa. Para o navegador, não. Vamos medir isso.
A prova: o mesmo aviso em .txt e em .html
O jsdom é uma implementação do parser de HTML e do DOM que roda dentro do
Node. Ele segue a mesma especificação que o Chrome e o Firefox seguem, então a
árvore que ele monta a partir de um arquivo é a árvore que o navegador montaria.
Dá para contar o que sobrou de cada arquivo depois da leitura:
import { readFileSync } from 'node:fs';
import { JSDOM } from 'jsdom';
const contar = (conteudo) => {
const { document } = new JSDOM(conteudo).window;
const elementos = document.body.querySelectorAll('*').length;
const textos = [...document.body.childNodes].filter((n) => n.nodeType === 3).length;
return { elementos, textos };
};
console.log('aviso.txt ->', contar(readFileSync('aviso.txt', 'utf8')));
console.log('aviso.html ->', contar(readFileSync('aviso.html', 'utf8')));Quatro linhas de texto viraram um nó no arquivo .txt: um bloco só, sem
divisão nenhuma. As mesmas quatro linhas marcadas viraram quatro elementos, cada
um com o próprio texto dentro. Marcação não é enfeite — é literalmente estrutura
de dados. Os quatro nós de texto que ainda aparecem no .html são só as quebras
de linha entre as tags.
A árvore fica visível quando você percorre os nós:
const { document } = new JSDOM(readFileSync('aviso.html', 'utf8')).window;
const desenhar = (no, nivel = 0) => {
for (const filho of no.childNodes) {
if (filho.nodeType === 1) {
console.log(`${' '.repeat(nivel)}<${filho.tagName.toLowerCase()}>`);
desenhar(filho, nivel + 1);
} else if (filho.nodeType === 3 && filho.textContent.trim()) {
console.log(`${' '.repeat(nivel)}"${filho.textContent.trim()}"`);
}
}
};
desenhar(document.body);Cada tag virou um galho; cada trecho de texto virou uma folha pendurada nele. Essa árvore tem nome: é o DOM, e é sobre ela que todo o resto do front-end trabalha. O caminho completo do texto até a tela é este:
Se quiser ver esse processo em detalhe, ele está destrinchado em como o navegador monta a árvore DOM.
Uma tag, um significado
Cada tag carrega um significado que o navegador conhece de antemão. h1 é o
título principal; h2 é um subtítulo dependente dele; p é parágrafo. Por isso
dá para perguntar à página coisas que o arquivo .txt não saberia responder:
const { document } = new JSDOM(readFileSync('aviso.html', 'utf8')).window;
const titulos = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
console.log('títulos encontrados:', titulos.length);
for (const t of titulos) console.log(` nível ${t.tagName[1]}: ${t.textContent}`);
console.log('parágrafos encontrados:', document.querySelectorAll('p').length);O navegador não leu o sentido das palavras. Ele leu as tags, que são o rótulo que você colou em cada trecho. O nome de cada tag e as informações extras que ela aceita estão detalhados em tags e atributos HTML.
A div é a tag que não quer dizer nada
Existe uma tag genérica, a div, que serve só para agrupar. Com CSS dá para
deixar uma página de div visualmente idêntica à outra. Só que idêntica na tela
não é idêntica na árvore:
const comDiv = `
<div>Livraria Página Sete</div>
<div>Rua das Acácias, 120 — aberto de terça a domingo</div>
<div>Clube de leitura</div>
<div>Toda quinta, às 19h, com café por conta da casa.</div>`;
const comSignificado = `
<h1>Livraria Página Sete</h1>
<p>Rua das Acácias, 120 — aberto de terça a domingo</p>
<h2>Clube de leitura</h2>
<p>Toda quinta, às 19h, com café por conta da casa.</p>`;
const analisar = (rotulo, html) => {
const { document } = new JSDOM(html).window;
const visivel = document.body.textContent.replace(/\s+/g, ' ').trim();
console.log(rotulo);
console.log(' caracteres de texto na tela:', visivel.length);
console.log(' títulos que o navegador reconhece:', document.querySelectorAll('h1,h2,h3,h4,h5,h6').length);
};
analisar('versão feita só de div:', comDiv);
analisar('versão com marcação:', comSignificado);Mesmos 135 caracteres na tela, zero títulos contra dois. Escolher a tag certa é a parte do HTML que dá trabalho e é a que separa página amadora de página profissional — assunto de HTML semântico.
Seu primeiro arquivo .html abre sem servidor nenhum
Um arquivo HTML é texto puro com outra extensão. Não tem compilação, não tem instalação, não tem terminal. Você salva e dá dois cliques.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<title>Livraria Página Sete</title>
</head>
<body>
<h1>Livraria Página Sete</h1>
<p>Rua das Acácias, 120 — de terça a domingo.</p>
<ul>
<li>Clube de leitura toda quinta</li>
<li>Sebo com troca de livros</li>
</ul>
<p><a href="/clube">Entrar no clube de leitura</a></p>
</body>
</html>O sistema operacional confirma que não há nada de especial nesse arquivo:
file pagina.html
wc -c pagina.html407 bytes de texto. E é disso que o navegador extrai o idioma da página, o nome que vai na aba e a lista de itens:
const dom = await JSDOM.fromFile('pagina.html');
const { document } = dom.window;
console.log('idioma declarado:', document.documentElement.lang);
console.log('codificação:', document.characterSet);
console.log('título da aba:', document.title);
console.log('filhos do <html>:', [...document.documentElement.children].map((e) => e.tagName).join(', '));
console.log('itens da lista:', document.querySelectorAll('li').length);O head guarda informação sobre a página e não aparece na tela; o body
guarda o que aparece. Cada peça dessa estrutura mínima está explicada em
estrutura de uma página HTML.
O que o navegador escreve por você
Aqui vem a parte que confunde quem está começando. Aquele esqueleto todo é obrigatório — mas se você não escrever, o navegador escreve sozinho:
// nenhuma dessas tags foi escrita: html, head, body
const { document } = new JSDOM('<h1>Livraria Página Sete</h1>').window;
console.log('estrutura montada:', document.documentElement.outerHTML);
console.log('o h1 caiu dentro de:', document.querySelector('h1').parentElement.tagName);Você entregou uma linha e recebeu três tags que nunca digitou. O parser de HTML é obrigado, pela especificação, a produzir uma árvore válida a partir de qualquer entrada — inclusive de uma entrada errada.
O erro mais comum: o navegador conserta a sua tag
Essa tolerância tem preço. Como o parser nunca reclama, o seu erro não vira mensagem: vira uma página silenciosamente diferente da que você imaginou.
const casos = {
'p sem fechar': '<p>Livro do mês<p>Autor da semana',
'div dentro de p': '<p>Promoção <div>50% off</div> até domingo</p>',
'li sem fechar': '<ul><li>Machado de Assis<li>Clarice Lispector</ul>',
};
for (const [nome, html] of Object.entries(casos)) {
const { document } = new JSDOM(html).window;
console.log(`${nome}:`);
console.log(` entrou: ${html}`);
console.log(` virou: ${document.body.innerHTML}`);
}O primeiro e o terceiro caso deram certo por sorte: o parser fechou as tags
exatamente onde você queria. O do meio é a armadilha. Você escreveu um
parágrafo com uma div dentro; o navegador devolveu um parágrafo curto, uma
div solta, um texto órfão e um parágrafo vazio no fim. Qualquer CSS escrito
para “o parágrafo da promoção” vai pegar só a palavra Promoção, e você vai
passar meia hora achando que o problema é o CSS.
Regra prática que evita quase todos esses casos: feche toda tag que você
abriu, e nunca coloque um bloco (div, ul, h2) dentro de um p.
O acento que vira Café
Existe uma linha do head que não é opcional. Sem ela, o navegador precisa
adivinhar em que codificação o arquivo foi salvo — e adivinha errado. Os dois
arquivos a seguir têm exatamente os mesmos bytes no body, os dois gravados em
UTF-8. Só o segundo avisa o navegador disso:
<!-- sem-charset.html -->
<!DOCTYPE html>
<html lang="pt-BR">
<head><title>Livraria</title></head>
<body><p>Café e leitura até as 22h</p></body>
</html>
<!-- com-charset.html: uma linha a mais, no head -->
<!DOCTYPE html>
<html lang="pt-BR">
<head><meta charset="UTF-8"><title>Livraria</title></head>
<body><p>Café e leitura até as 22h</p></body>
</html>for (const arquivo of ['sem-charset.html', 'com-charset.html']) {
const dom = await JSDOM.fromFile(arquivo);
const { document } = dom.window;
console.log(`${arquivo}`);
console.log(' codificação adotada:', document.characterSet);
console.log(' texto exibido:', document.querySelector('p').textContent);
}Esse é o famoso acento quebrado. Repare que o palpite do arquivo sem meta foi
windows-1252: sem a declaração, quem lê o arquivo escolhe uma codificação
qualquer, e a escolha muda conforme o programa e o idioma do sistema. É por isso
que a mesma página às vezes aparece certa na sua máquina e quebrada na do
cliente. O diagnóstico completo, com o aviso que o Firefox mostra no console,
está em
the character encoding of the HTML document was not declared.
HTML, CSS e JavaScript: quem faz o quê
As três tecnologias trabalham sobre a mesma árvore, em papéis diferentes:
| tecnologia | responde a pergunta | o que acontece se faltar |
|---|---|---|
| HTML | o que é cada coisa | não há página nenhuma |
| CSS | qual a aparência de cada coisa | a página fica com o visual padrão do navegador |
| JavaScript | o que muda depois que a página abriu | a página fica parada, mas funcionando |
A ordem de aprendizado sai daí: HTML primeiro, porque os outros dois precisam de
uma árvore para trabalhar. O JavaScript, por exemplo, encontra um nó pelo id e
troca o conteúdo dele:
const html = `<!DOCTYPE html>
<html lang="pt-BR">
<head><title>Livraria Página Sete</title></head>
<body>
<h1 id="vitrine">Livro do mês: Grande Sertão</h1>
<script>
const titulo = document.querySelector('#vitrine');
titulo.textContent = 'Livro do mês: Vidas Secas';
</script>
</body>
</html>`;
const { document } = new JSDOM(html, { runScripts: 'dangerously' }).window;
console.log('o que o JavaScript deixou na tela:', document.querySelector('#vitrine').textContent);O arquivo continua dizendo “Grande Sertão”. Quem mudou foi a árvore, em memória. Essa divisão de trabalho continua na trilha de CSS e em o que é JavaScript, as duas camadas que vêm depois desta.
Quem mais lê o seu HTML
O navegador não é o único leitor. Leitor de tela, robô de busca e programa de e-mail consomem a mesma árvore, e cada um extrai a parte que interessa a ele:
const { document } = new JSDOM(`
<h1>Livraria Página Sete</h1>
<img src="/fachada.jpg" alt="Fachada da livraria com toldo verde">
<img src="/estante.jpg">
<h2>Clube de leitura</h2>
<p>Encontro toda quinta. <a href="/clube">Ver a lista de livros</a>.</p>
<p><a href="/contato">Falar com a gente</a></p>`).window;
console.log('sumário de títulos:');
for (const t of document.querySelectorAll('h1,h2,h3')) {
console.log(` ${'-'.repeat(Number(t.tagName[1]))} ${t.textContent}`);
}
console.log('links da página:');
for (const a of document.querySelectorAll('a[href]')) {
console.log(` ${a.textContent} -> ${a.getAttribute('href')}`);
}
console.log('imagens:');
for (const img of document.querySelectorAll('img')) {
console.log(` ${img.getAttribute('src')} anunciada como: ${img.alt || '(sem alt: só o arquivo)'}`);
}Esse é literalmente o menu que um leitor de tela oferece a quem não enxerga a
página: pular de título em título, listar os links, ouvir a descrição das
imagens. A imagem sem alt é a única que não tem o que anunciar. E o Google faz
a mesma leitura para decidir do que a página trata.
O que você já consegue montar hoje
Com h1 a h6, p, ul/li e a, você já publica a página inteira da
livraria: nome, endereço, horário, lista de eventos e link de contato. É pouca
tag, e é o esqueleto de quase todo site que existe.
O próximo passo é entender o arquivo por inteiro, linha a linha, em estrutura de uma página HTML. Se preferir ver o mapa completo antes de continuar, o guia de HTML do zero mostra a ordem de estudo e onde cada assunto entra.
Prefere aprender em vídeo?
Tem aula sobre este assunto no nosso canal.
DevClub no YouTube🔴 APRENDA O QUE É HTML E CRIE SEU PRIMEIRO SITE - BÔNUS INCRÍVEL NO FINALAssistir a aula
DevClub no YouTubeCurso de HTML | Aprenda HTML em 15 minutos | Seu Primeiro siteAssistir a aula
Perguntas frequentes
Qual a diferença entre HTML e HTML5?
Preciso instalar algum programa para escrever HTML?
Dá para publicar um site só com HTML, sem CSS e sem JavaScript?
Por que o navegador abriu meu arquivo mostrando as tags como texto?
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 com jsdom 30.0.1, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Structuring content with HTML — developer.mozilla.org
- WHATWG — HTML Standard: Parsing HTML documents — html.spec.whatwg.org


