Guia completo
HTML do zero: o guia completo da sua primeira página
O que é HTML, qual tag usar para cada coisa, o que o navegador conserta sozinho no seu markup e em que ordem estudar cada assunto da trilha.
HTML é a linguagem que descreve o conteúdo de uma página: isto é um título, aquilo é um parágrafo, aquele bloco é uma lista, aquele campo espera um e-mail. Ele não pinta, não anima e não calcula — só diz o que cada pedaço da página é, e o navegador constrói uma árvore de objetos a partir dessa descrição.
Este guia é o mapa da trilha de HTML. Ele diz o que estudar, em que ordem, qual tag usar para cada intenção, e mostra — com código executado — o que o navegador faz com o seu markup quando você erra. Todos os exemplos são de uma página só, a da Escola de Música Aurora, uma escola de bairro que dá aula de violão, piano e canto. A página tem cabeçalho, lista de cursos, tabela de horários, foto da fachada e formulário de matrícula. É pequena o bastante para caber num artigo e grande o bastante para encostar em quase toda tag que importa.
O navegador transforma o texto HTML em DOM, uma árvore de objetos com relações de pai, filho e irmão. Em palavras simples, as tags são instruções de estrutura; o DOM é a organização viva que o navegador constrói para renderizar e disponibilizar à acessibilidade e ao JavaScript.
O roteiro que o navegador transforma em árvore
Pense no roteiro de uma peça: ele identifica título, falas, cenas e entradas, mas não é o palco pronto. A equipe interpreta esse roteiro e monta uma estrutura que pode ser iluminada e movimentada. O arquivo HTML é o roteiro; o DOM é a estrutura montada pelo navegador. CSS e JavaScript trabalham sobre essa árvore, não sobre uma fotografia do arquivo.
Na primeira página do guia, conte quantos elementos você escreveu e depois conte os nós exibidos no painel Elements. Se os números ou a hierarquia divergirem, procure o ponto em que o parser corrigiu ou completou o markup. Essa diferença é uma observação concreta do trabalho do navegador.
Marcação é isto: você envolve um pedaço de conteúdo com uma etiqueta que diz o
que ele significa. <h1>Escola de Música Aurora</h1> não quer dizer “escreva
isso grande e em negrito”. Quer dizer “este é o título principal desta página”.
Que ele apareça grande é uma consequência do estilo padrão do navegador, e você
pode mudar isso em uma linha de CSS sem mudar o significado.
Essa diferença entre significado e aparência é a única ideia realmente
difícil do HTML, e é ela que separa quem escreve markup de quem empilha div.
Um <div> com fonte 32px parece um título na tela e não é um título para
ninguém: nem para o leitor de tela, nem para o Google, nem para o atalho de
navegação por cabeçalhos, nem para a pessoa que vai manter o código.
O que o HTML não faz, e é onde as três primeiras semanas de estudo geram confusão:
- Não define aparência. Cor, espaço, alinhamento, fonte, animação: tudo é CSS. O navegador aplica um estilo padrão mínimo só para o texto não sair todo igual, e esse padrão existe para ser substituído.
- Não tem lógica. Não existe
if, não existe repetição, não existe variável. Se a página precisa somar, decidir ou reagir a um clique, isso é JavaScript. - Não reclama. E este é o ponto que mais custa caro. HTML mal escrito não quebra: o navegador conserta em silêncio e segue. Você só descobre o problema quando o CSS não pega ou o formulário não envia o que devia.
Em compensação, o HTML sozinho já faz mais do que a maioria imagina: valida formulário, toca vídeo, carrega imagem sob demanda, monta o preview do link no WhatsApp e navega entre âncoras. Boa parte do JavaScript que iniciante escreve existe para refazer, pior, algo que uma tag já entregava pronto.
A primeira página: um arquivo e nenhuma instalação
Você não precisa instalar nada. Abra o bloco de notas ou o VS Code, salve um
arquivo com a extensão .html e dê dois cliques nele. O navegador que já está
aberto agora é o único ambiente necessário.
Esta é a menor página honesta que existe:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Escola de Música Aurora</title>
</head>
<body>
<h1>Escola de Música Aurora</h1>
<p>Aulas de violão, piano e canto no Butantã.</p>
</body>
</html>Cinco elementos aparecem aí e nenhum é decoração. <!DOCTYPE html> é o mais
estranho dos cinco — não é nem uma tag de verdade, é uma instrução para o
navegador. E ela muda o comportamento do documento inteiro. Dá para provar sem
abrir o navegador, lendo a propriedade document.compatMode, que diz em qual
modo de renderização a página caiu:
import { JSDOM } from 'jsdom';
const semDoctype = new JSDOM('<title>Aurora</title><p>Matrículas abertas.');
const comDoctype = new JSDOM('<!DOCTYPE html><title>Aurora</title><p>Matrículas abertas.');
console.log('sem <!DOCTYPE html> ->', semDoctype.window.document.compatMode);
console.log('com <!DOCTYPE html> ->', comDoctype.window.document.compatMode);
console.log('título que o navegador leu:', comDoctype.window.document.title);
console.log('corpo montado:', comDoctype.window.document.body.innerHTML);BackCompat é o modo de compatibilidade, apelidado de quirks mode: o
navegador finge ser o Internet Explorer 5 para não quebrar sites de 1998. Nesse
modo o box model do CSS muda, e larguras e alturas passam a ser calculadas de
outro jeito. CSS1Compat é o modo padrão, que é o que você quer. Quinze
caracteres decidem entre os dois.
Repare também que o <p> sem fechamento virou <p>Matrículas abertas.</p> na
saída. Guarde isso: vamos voltar a esse conserto automático mais adiante, porque
ele é responsável por uma classe inteira de bugs difíceis.
Os outros três elementos do arquivo mínimo têm cada um a sua função. lang="pt-BR"
diz o idioma do conteúdo — é o que faz o leitor de tela pronunciar “matrícula”
com sotaque de português e o navegador oferecer tradução. <meta charset="UTF-8">
declara a codificação dos bytes. E <title> é o texto da aba, do favorito e do
resultado de busca. A anatomia completa desse arquivo está em
estrutura de uma página HTML.
As 20 tags que resolvem 90% das páginas
A especificação do HTML tem mais de cem elementos. Ninguém usa cem. Para medir quantos realmente aparecem no mundo, rodei um censo em cinco páginas públicas bem diferentes entre si: a documentação de HTML da MDN, o verbete de HTML na Wikipédia em português, o portal gov.br, a própria especificação do WHATWG e a home do UOL.
import { JSDOM } from 'jsdom';
const paginas = [
'https://developer.mozilla.org/pt-BR/docs/Web/HTML',
'https://pt.wikipedia.org/wiki/HTML',
'https://www.gov.br/pt-br',
'https://html.spec.whatwg.org/multipage/',
'https://www.uol.com.br/',
];
const contagem = new Map();
let total = 0;
for (const url of paginas) {
const resposta = await fetch(url, { headers: { 'user-agent': 'Mozilla/5.0 (censo-html)' } });
const dom = new JSDOM(await resposta.text());
const elementos = dom.window.document.querySelectorAll('*');
for (const el of elementos) {
const tag = el.tagName.toLowerCase();
contagem.set(tag, (contagem.get(tag) ?? 0) + 1);
total++;
}
console.log(url, '->', elementos.length, 'elementos');
}
const ordenado = [...contagem].sort((a, b) => b[1] - a[1]);
console.log('\ntags distintas usadas:', ordenado.length);
console.log('elementos contados:', total);
let acumulado = 0;
ordenado.slice(0, 20).forEach(([tag, n], i) => {
acumulado += n;
console.log(String(i + 1).padStart(2), tag.padEnd(10), String(n).padStart(6), (100 * acumulado / total).toFixed(1) + '%');
});tags distintas usadas: 94 elementos contados: 13383 1 a 3102 23.2% 2 span 2833 44.3% 3 li 2661 64.2% 4 div 1308 74.0% 5 code 743 79.6% 6 source 316 81.9% 7 img 225 83.6% 8 ol 212 85.2% 9 ul 178 86.5% 10 figure 168 87.8% 11 picture 160 89.0% 12 h3 140 90.0% 13 article 132 91.0% 14 link 107 91.8% 15 script 101 92.6% 16 p 99 93.3% 17 meta 89 94.0% 18 var 76 94.5% 19 section 67 95.0% 20 i 53 95.4%
Treze mil elementos, noventa e quatro tags distintas — e as vinte mais usadas
cobrem 95,4% de tudo. As doze primeiras já cobrem 90%. É um recorte pequeno e
enviesado (a especificação do WHATWG puxa code e var para cima; a Wikipédia
puxa li), mas a forma da curva é a mesma em qualquer amostra que você medir: um
punhado de tags faz quase todo o trabalho, e a cauda longa é consulta. Rode você
mesmo e os números vão sair diferentes — são páginas vivas, e a saída acima é a
que apareceu aqui quando este guia foi revisado.
A leitura prática disso é dupla. Primeiro: decorar tag é a menor parte do
serviço. Se vinte tags resolvem, você aprende as vinte em uma tarde e passa o
resto do tempo no que é difícil de verdade. Segundo, e mais incômodo: div e
span juntos são 31% dos elementos dessas páginas. Uma boa parte disso é
inevitável — são as tags neutras, que existem justamente para agrupar sem
significar nada. Outra boa parte é gente usando div onde existia tag com nome.
Qual tag usar para cada intenção
Esta é a tabela que eu daria para um aluno no segundo dia. Ela não lista tags
por ordem alfabética: lista intenções, porque a pergunta real de quem está
montando uma página nunca é “o que faz a tag <article>”, e sim “com o que eu
escrevo isto aqui?”. A terceira coluna é o atalho que aparece no código de quem
ainda não fez a escolha consciente.
| a sua intenção | a tag certa | o atalho que trava depois |
|---|---|---|
| título principal da página, uma vez só | <h1> |
<div class="titulo"> com fonte grande |
| título de uma seção | <h2> a <h6>, sem pular nível |
ir de h1 para h4 porque “ficou do tamanho certo” |
| um bloco de texto corrido | <p> |
dois <br> entre as frases |
| quebrar linha dentro do mesmo bloco (endereço, verso) | <br> |
um <p> vazio |
| lista sem ordem (cursos, benefícios) | <ul> com <li> |
<div> com a bolinha desenhada no CSS |
| lista em que a ordem importa (passos, colocação) | <ol> com <li> |
<ul> com o número digitado à mão |
| dado tabular de verdade (horário, preço) | <table> com <thead> e <tbody> |
<div> com Flexbox imitando colunas |
| levar para outra página ou âncora | <a href> |
<button> que muda a URL no clique |
| executar uma ação na própria página | <button type="button"> |
<a href="#"> com JavaScript grudado |
| imagem que faz parte do conteúdo | <img> com alt descritivo |
background-image no CSS |
| imagem puramente decorativa | <img alt=""> ou fundo no CSS |
alt="imagem", alt="foto" |
| ícone, logo, gráfico simples | <svg> inline |
PNG num <img> que borra no zoom |
| cabeçalho do site ou de um bloco | <header> |
<div id="header"> |
| menu de navegação | <nav> com <ul> dentro |
vários <a> soltos dentro de uma <div> |
| o conteúdo único daquela página | <main>, um por página |
<div class="container"> |
| um item que faz sentido sozinho (card de curso, post) | <article> |
<div class="card"> |
| um bloco temático dentro da página | <section> com um título dentro |
<section> sem cabeçalho nenhum |
| rodapé | <footer> |
<div class="rodape"> |
| rótulo de um campo de formulário | <label for="id"> |
texto solto antes do input |
| agrupar campos relacionados | <fieldset> com <legend> |
<div> com um título em negrito |
| destacar por importância ou ênfase | <strong>, <em> |
<b>, <i> por hábito |
| trecho de código ou tecla | <code>, <kbd> |
<span class="codigo"> |
A regra por trás da tabela, e a única que você precisa levar: escolha a tag
pelo que o conteúdo é, nunca pelo que ele parece na tela. Se a resposta honesta
for “não é nada em especial, só preciso agrupar para estilizar”, aí <div> é a
escolha certa — ela existe exatamente para isso, e forçar <section> no lugar é
o erro oposto, igualmente comum em quem acabou de descobrir semântica.
Duas linhas dessa tabela merecem um aviso extra, porque geram bug real e não só
código feio. <a> e <button> não são intercambiáveis: o link é navegação, abre
em nova aba com Ctrl, aparece na lista de links do leitor de tela; o botão é
ação, dispara com Espaço além do Enter, e dentro de um formulário envia. Trocar
um pelo outro quebra teclado. E <table> para layout é o pecado dos anos 2000
que voltou disfarçado de display: grid em cima de <div> — a diferença é que
a tabela de verdade tem cabeçalho de coluna, e o leitor de tela anuncia esse
cabeçalho a cada célula.
O que o navegador conserta no seu markup sem avisar
Agora a parte que ninguém conta no primeiro dia. O navegador nunca mostra erro de HTML. Ele recebe o texto, aplica um algoritmo de recuperação definido na especificação e monta a árvore que der. Essa árvore é o DOM — e é ela, não o seu arquivo, que o CSS estiliza e o JavaScript enxerga.
Esta é a página da Escola Aurora do jeito que sai da mão de quem está começando:
<html>
<title>Escola de Música Aurora</title>
<div class="titulo"><b>Escola de Música Aurora</b></div>
<p>Aulas de violão, piano e canto no bairro do Butantã.
<p>Matrículas abertas para a turma de março.
<ul>
<li>Violão — segunda, 19h
<li>Piano — quarta, 18h
</ul>
<table>
<tr><td>Curso</td><td>Professor</td></tr>
<tr><td>Violão</td><td>Marina Duarte</td></tr>
</table>
<img src="fachada.jpg">
<form>
Seu nome: <input type="text" name="nome"><br>
<input type="submit" value="Quero me matricular">
</form>
</html>Sem doctype, sem <head>, sem <body>, com parágrafos e itens de lista abertos.
Para ver o que o navegador realmente guardou, escrevi um script que percorre a
árvore montada e imprime só os elementos, indentados pela profundidade:
import { JSDOM } from 'jsdom';
import { readFileSync } from 'node:fs';
const dom = new JSDOM(readFileSync(process.argv[2], 'utf8'));
function imprimir(no, nivel = 0) {
for (const filho of no.childNodes) {
if (filho.nodeType === 1) {
console.log(' '.repeat(nivel) + '<' + filho.tagName.toLowerCase() + '>');
imprimir(filho, nivel + 1);
}
}
}
imprimir(dom.window.document.documentElement.parentNode);Conte quantas coisas o navegador fez por conta própria. Criou um <head> que não
existia e mudou o <title> para dentro dele. Criou um <body> e jogou o resto
lá. Fechou os dois <p> sozinho. Fechou os dois <li> sozinho. E — a mais
famosa — inventou um <tbody> que você não escreveu, entre <table> e
<tr>. Esse tbody fantasma é a razão de table > tr não casar com nada quando
a tabela vem escrita no HTML: o parser sempre insere o tbody, e no DOM o tr
fica neto de table, não filho. Escreva table tr ou tbody > tr. O mecanismo
inteiro dessa montagem está detalhado em
como o navegador monta a árvore DOM.
O conserto é generoso, e é por isso que a web de 1996 ainda abre. Mas ele
também esconde o problema. Para enxergar antes do usuário, existe validador. O
html-validate roda no terminal e aponta linha e coluna:
npx html-validate aurora-errado.htmlDez erros numa página de dezenove linhas, e nenhum deles apareceu no navegador.
Agora a mesma página escrita com as tags que a tabela da seção anterior manda,
salva ao lado da outra como aurora.html:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Escola de Música Aurora — aulas no Butantã</title>
</head>
<body>
<header>
<h1>Escola de Música Aurora</h1>
<nav>
<ul>
<li><a href="#cursos">Cursos</a></li>
<li><a href="#matricula">Matrícula</a></li>
</ul>
</nav>
</header>
<main>
<p>Aulas de violão, piano e canto no bairro do Butantã.</p>
<section id="cursos">
<h2>Cursos e horários</h2>
<table>
<thead>
<tr><th scope="col">Curso</th><th scope="col">Professor</th></tr>
</thead>
<tbody>
<tr><td>Violão</td><td>Marina Duarte</td></tr>
</tbody>
</table>
<img src="fachada.jpg" alt="Fachada da escola, com portão azul e placa de neon" width="640" height="360">
</section>
<section id="matricula">
<h2>Matrícula</h2>
<form action="/matricula" method="post">
<label for="nome">Seu nome</label>
<input type="text" id="nome" name="nome" required>
<button type="submit">Quero me matricular</button>
</form>
</section>
</main>
<footer>
<p>Rua das Oliveiras, 120 — São Paulo</p>
</footer>
</body>
</html>Passando essa versão pelo mesmo script, a árvore que sai é a árvore que está escrita — nenhum elemento fantasma, nada mudado de lugar:
Essa segunda árvore passa no validador sem uma linha de saída. E a leitura dela já conta a página inteira: dá para entender o documento sem ler uma palavra do conteúdo, o que é exatamente o que um leitor de tela e um robô de busca fazem.
Quando o conserto vira armadilha
Fechar <p> sozinho é inofensivo. Já esquecer um </section> produz uma árvore
completamente diferente da que você imaginou — e o navegador não avisa:
<main>
<section class="cursos">
<h2>Cursos</h2>
<p>Violão, piano e canto.</p>
<section class="matricula">
<h2>Matrícula</h2>
</section>
</main>A segunda section virou filha da primeira. Escrevendo .cursos { padding: 2rem }
você acaba com o dobro de padding na matrícula, e vai passar meia hora
procurando o erro no CSS — onde ele não está. Este é o motivo pelo qual “meu CSS
não pega” tantas vezes é um problema de HTML.
O formulário: o que o navegador realmente envia
Formulário é a parte do HTML que mais parece funcionar e menos é entendida. O
navegador monta o pacote de envio a partir de uma regra curta: entra no envio
todo campo que tem name, está habilitado e — no caso de caixa e rádio — está
marcado. Nada de id, nada de class, nada de posição na tela.
Dá para ver o pacote exato antes de enviar, construindo um FormData a partir do
formulário. Este é o formulário de matrícula da Aurora com três campos plantados
para cair fora do envio, cada um por um motivo diferente:
import { JSDOM } from 'jsdom';
const dom = new JSDOM(`<!DOCTYPE html>
<form id="matricula" action="/matricula" method="post">
<input type="text" name="aluno" value="Marina Duarte">
<input type="email" name="email" value="marina@aurora.com.br">
<select name="curso"><option value="violao" selected>Violão</option></select>
<input type="checkbox" name="newsletter" checked>
<input type="checkbox" name="whatsapp">
<input type="text" name="turma" value="noite" disabled>
<input type="text" value="turma da noite">
</form>`);
const form = dom.window.document.getElementById('matricula');
const dados = new dom.window.FormData(form);
for (const [chave, valor] of dados) {
console.log(chave, '=', valor);
}
console.log('campos na tela:', form.querySelectorAll('input, select').length);
console.log('campos enviados:', [...dados].length);Sete campos na tela, quatro no envio, e cada ausência tem uma causa diferente.
O whatsapp é uma caixa desmarcada: checkbox desmarcado não envia nada — não
envia off, não envia string vazia, simplesmente não aparece na lista. O turma
tem name e tem valor, mas está disabled, e campo desabilitado fica de fora
mesmo preenchido — é o mais traiçoeiro dos três, porque o valor está lá na tela.
E o último não tem name nenhum. Os três juntos são a origem do bug de “o campo
está lá, o usuário preenche, e chega vazio no servidor”.
Repare ainda em duas coisas que costumam surpreender: o <select> envia o value da
opção (violao), não o texto que aparece (Violão); e a caixa marcada envia a
string on, que é o valor padrão de um checkbox sem value próprio. Os detalhes
de cada campo estão em formulário em HTML.
Acessibilidade não é a última etapa do projeto
A frase que eu mais ouço é “depois eu ajusto a acessibilidade”. Não existe
depois: quase tudo que torna uma página acessível é a escolha de tag que você já
está fazendo agora, de graça. Usar <button> no lugar de <div onclick> já
entrega foco por teclado, acionamento por Espaço e anúncio de “botão”. Usar
<h2> de verdade já entrega o atalho de navegação por cabeçalhos.
O caso mais simples de demonstrar é o rótulo de campo. Na versão errada da
página, o texto “Seu nome:” está solto antes do input — visualmente parece um
rótulo, e para o navegador não é nada. Na versão certa, o <label for="nome">
aponta para o id do campo. A propriedade labels, que todo campo rotulável
expõe, mostra a diferença:
import { JSDOM } from 'jsdom';
import { readFileSync } from 'node:fs';
for (const arquivo of ['aurora-errado.html', 'aurora.html']) {
const dom = new JSDOM(readFileSync(arquivo, 'utf8'));
const campo = dom.window.document.querySelector('input[name="nome"]');
const rotulos = [...campo.labels].map((l) => l.textContent.trim());
console.log(arquivo, '->', rotulos.length, 'rótulo(s):', rotulos);
}Zero contra um. Na página da esquerda, quem usa leitor de tela chega no campo e
ouve “caixa de edição” — sem saber o que digitar ali. E não é só acessibilidade:
com <label for>, clicar no texto foca o campo, o que aumenta a área de toque no
celular e melhora a conversão do formulário para todo mundo.
Quatro hábitos que resolvem a maior parte do problema e não custam tempo nenhum se forem adquiridos agora:
- Um
altque descreve, não que nomeia.alt="Fachada da escola, com portão azul e placa de neon"serve;alt="fachada.jpg"não serve. Imagem decorativa levaalt=""vazio, que é diferente de não teralt. - Um
<h1>por página, e sem pular níveis. A hierarquia de títulos é o sumário do documento. - Todo campo com
<label for>. Ou, quando o rótulo visual não couber no design,aria-labelno campo. - Nunca desligue o contorno de foco. Aquele anel que aparece ao navegar de Tab é o cursor de quem não usa mouse. Substitua por um mais bonito se quiser, mas não apague.
O aprofundamento — atributos ARIA, ordem de foco, role — está em
acessibilidade em HTML, e faz mais sentido
depois que a semântica estiver firme.
A ordem de estudo desta trilha, seção por seção
A trilha de HTML está organizada em seis blocos. A ordem importa: cada bloco assume que o anterior já está resolvido, e pular direto para formulário ou para semântica é a receita para decorar sem entender.
1. Fundamentos. O que é HTML, a estrutura do arquivo, a anatomia de uma tag
com seus atributos, e o <head> inteiro — charset, viewport, description. É o
bloco mais curto e o que mais evita dor depois. Aqui mora também o primeiro erro
de console que todo brasileiro encontra, o do encoding não declarado.
2. Texto. Títulos de h1 a h6, parágrafos, <br>, <hr>, negrito e
itálico com significado, citação, código, e as entidades como &nbsp; e
&amp;. Parece bobo e não é: é onde você exercita a diferença entre marcar
por aparência e marcar por sentido, em conteúdo simples.
3. Conteúdo. Links (âncora, nova aba, mailto), imagens (alt, dimensões,
carregamento tardio), listas e tabelas de verdade, com thead, tbody,
colspan e rowspan. Ao fim deste bloco você já monta a página da Aurora
inteira, menos o formulário.
4. Formulários. <form>, <label>, os vinte e dois tipos de input que a
especificação define — e a meia dúzia que resolve quase todo formulário — e a
validação que o navegador faz sozinho com required, pattern, min e max. Este é o
bloco mais útil profissionalmente: formulário é o que quase todo site precisa e
quase todo iniciante entrega errado.
5. Semântica e acessibilidade. header, nav, main, section, article,
aside, footer — e o critério para escolher entre eles, que está em
HTML semântico. Junto vem a acessibilidade, pelo
motivo que a seção anterior deste guia mostrou: os dois assuntos são o mesmo
assunto visto de dois ângulos.
6. Mídia e integração. Vídeo e áudio nativos, <iframe> para mapa e YouTube,
SVG e canvas, data-* para guardar informação no elemento, Open Graph para o
preview do link no WhatsApp, e o comportamento de defer e async no
<script>. É o bloco que transforma uma página em site de verdade.
Espalhados entre os blocos estão os artigos de erro — o encoding não declarado, o 404 no console — e os dois artigos de conceito, sobre a montagem do DOM e sobre o carregamento de script. Eles não são leitura obrigatória na primeira passada: leia quando o problema aparecer, que é quando o texto gruda.
Com uma hora por dia, os blocos 1 a 3 levam mais ou menos uma semana, o 4 leva outra, e os blocos 5 e 6 se resolvem em uma terceira — sempre com página aberta no navegador, nunca só lendo.
Onde quase todo aluno trava
Depois de muita gente passando por esta trilha, os pontos de parada se repetem com uma regularidade quase entediante. São cinco, e nenhum é falta de inteligência.
Acento virando símbolo estranho. O campeão absoluto no Brasil. O arquivo é salvo em UTF-8, a página não declara o charset, e o navegador tenta adivinhar. Os mesmos bytes lidos com a tabela errada viram outra coisa:
const titulo = 'Escola de Música Aurora — Violão e Canto';
const bytes = Buffer.from(titulo, 'utf8');
console.log('bytes gravados no arquivo:', bytes.length);
console.log('lido como UTF-8: ', new TextDecoder('utf-8').decode(bytes));
console.log('lido como windows-1252:', new TextDecoder('windows-1252').decode(bytes));O arquivo está certo, o texto está certo, a leitura é que está errada. Uma linha
— <meta charset="UTF-8"> como primeira coisa dentro do <head> — resolve
permanentemente.
Div para tudo. A pessoa aprende <div> no segundo dia, descobre que ela
serve para qualquer coisa, e nunca mais sai dali. O sintoma é uma página inteira
de <div class="..."> aninhados. O remédio não é decorar as tags semânticas: é
usar a tabela de intenções deste guia toda vez que abrir uma tag nova, por umas
duas semanas, até virar automático.
Tratar HTML como etapa descartável e pular para o CSS. É o erro mais caro da lista. Como o navegador conserta tudo em silêncio, a pessoa acredita que domina o assunto porque a página “aparece”. Aí chega no CSS, o seletor não funciona, e ela procura a resposta no lugar errado. Se você não consegue desenhar a árvore da sua própria página no papel, você ainda não terminou o HTML.
Confundir id com class. id é único na página e serve de alvo para
âncora e para <label for>. class se repete à vontade e serve para estilo. Usar
id para estilizar funciona, e cria uma dívida de especificidade que aparece
semanas depois, quando a regra recusa a ser sobrescrita.
Copiar markup sem entender. Pegar um trecho pronto e colar acelera hoje e custa amanhã. A pergunta que salva é sempre a mesma: “por que esta tag e não outra?” Se você não souber responder para uma linha do seu próprio arquivo, aquela linha ainda não é sua.
O que estudar depois: CSS, JavaScript e o resto
Com a página da Aurora de pé, com estrutura correta e passando no validador, o próximo passo é evidente: ela está feia. Feiura é problema de CSS, e é para lá que você vai.
CSS, imediatamente depois. Seletor, box model, Flexbox, Grid e responsividade.
O HTML fica muito mais fácil de entender quando você estiliza o que escreveu — é
estilizando que você percebe, na prática, por que aquela section extra virou
filha da outra. O caminho continua na
trilha de CSS.
JavaScript, depois do CSS. Só faz sentido programar a página quando ela já
existe e já está montada. O DOM que este guia mostrou é exatamente o objeto que o
JavaScript manipula: querySelector busca nessa árvore, addEventListener
escuta nesses elementos. Quem chega no JavaScript com HTML firme entende DOM em
uma tarde; quem chega sem, acha que é mágica. O
guia de JavaScript segue a partir daí.
Um projeto pequeno e terminado. Antes de qualquer framework, monte uma página real de ponta a ponta: a da escola do seu bairro, a do seu portfólio, o cardápio de um restaurante. Terminada, validada, publicada. Um projeto concluído ensina mais do que cinco começados, e é a única coisa que alguém consegue avaliar.
O que não vale antecipar: framework de CSS, gerador de site estático, biblioteca de componentes. Todos escondem o HTML atrás de uma camada de abstração, e esconder o que você ainda não entendeu é a forma mais rápida de ficar parado.
Para começar agora e na ordem certa, entre pela trilha de HTML do zero: as lições estão numeradas, cada uma tem código executado e o erro mais comum dela reproduzido.
Trilha
HTML
A estrutura da página, tag por tag, do jeito que o navegador e o Google leem.
- 01O que é HTML e como o navegador transforma tag em página
- 02Estrutura de uma página HTML: doctype, html, head e body
- 03Tags e atributos HTML: anatomia de um elemento
- 04Meta tags no HTML: charset, viewport e o resto do head
- 05Títulos e parágrafos em HTML: h1 a h6, p, br e hr
- 06Formatar texto em HTML: negrito, itálico, código e citação
- 07Entidades HTML: , & e os caracteres especiais
- 08Links em HTML: href, target, âncora e link de e-mail
- 09Imagens em HTML: img, alt, tamanho e carregamento lento
- 10Listas em HTML: ul, ol, li e lista de definição
- 11Tabelas em HTML: table, thead, tbody, colspan e rowspan
- 12Formulário em HTML: form, label e o que o navegador envia
- 13Tipos de input HTML: qual campo usar para cada dado
- 14Validação de formulário sem JavaScript: required e pattern
- 15HTML semântico: header, nav, main, section, article e footer
- 16Acessibilidade em HTML: alt, label, foco e atributos ARIA
- 17Vídeo e áudio em HTML: tag video, controls e autoplay
- 18iframe no HTML: incorporar YouTube, mapa e outra página
- 19SVG e canvas no HTML: qual usar para desenhar na página
- 20data attributes no HTML: guardar dado no próprio elemento
- 21Open Graph no HTML: o preview do link no WhatsApp
html
script defer e async: como o HTML bloqueia o carregamento
Failed to load resource: 404 (Not Found) no console
Como o navegador monta a árvore DOM a partir do seu HTML
The character encoding of the HTML document was not declared
Perguntas frequentes
HTML é uma linguagem de programação?
Quanto tempo leva para aprender HTML?
Preciso decorar todas as tags?
Posso aprender HTML e CSS ao mesmo tempo?
HTML5 é diferente de HTML?
Vale usar IA para escrever o HTML por mim?
O código deste guia foi executado em Node 24.16.0 com jsdom 30.0.1 e html-validate 11.9.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN Web Docs — HTML: linguagem de marcação de hipertexto — developer.mozilla.org
- HTML Living Standard — WHATWG — html.spec.whatwg.org
- html-validate — regras de validação de HTML — html-validate.org



