Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA

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.

21 lições em trilha25 artigos no guiaAtualizado em agosto de 2026

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:

html
<!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:

js
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);
sem <!DOCTYPE html> -> BackCompat com <!DOCTYPE html> -> CSS1Compat título que o navegador leu: Aurora corpo montado: <p>Matrículas abertas.</p>

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.

js
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) + '%');
});
https://developer.mozilla.org/pt-BR/docs/Web/HTML -> 1599 elementos https://pt.wikipedia.org/wiki/HTML -> 2387 elementos https://www.gov.br/pt-br -> 982 elementos https://html.spec.whatwg.org/multipage/ -> 4467 elementos https://www.uol.com.br/ -> 3948 elementos

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
<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:

js
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);
<html> <head> <title> <body> <div> <b> <p> <p> <ul> <li> <li> <table> <tbody> <tr> <td> <td> <tr> <td> <td> <img> <form> <input> <br> <input>

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:

bash
npx html-validate aurora-errado.html
aurora-errado.html 1:2 error <html> is missing required "lang" attribute element-required-attributes 2:1 error Element <head> is implicitly closed by adjacent <div> no-implicit-close 3:1 error Element <body> is implicitly closed by parent </html> no-implicit-close 4:2 error Element <p> is implicitly closed by sibling no-implicit-close 5:2 error Element <p> is implicitly closed by adjacent <ul> no-implicit-close 7:4 error Element <li> is implicitly closed by sibling no-implicit-close 8:4 error Element <li> is implicitly closed by parent </ul> no-implicit-close 11:4 error Prefer to wrap <tr> elements in <tbody> prefer-tbody 14:2 error <img> is missing required "alt" attribute wcag/h37 17:16 error Prefer to use <button> instead of <input type="submit"> when adding buttons prefer-button

Dez 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:

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:

<html> <head> <meta> <meta> <title> <body> <header> <h1> <nav> <ul> <li> <a> <li> <a> <main> <p> <section> <h2> <table> <thead> <tr> <th> <th> <tbody> <tr> <td> <td> <img> <section> <h2> <form> <label> <input> <button> <footer> <p>

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:

html
<main>
  <section class="cursos">
    <h2>Cursos</h2>
    <p>Violão, piano e canto.</p>
  <section class="matricula">
    <h2>Matrícula</h2>
  </section>
</main>
<html> <head> <body> <main> <section> <h2> <p> <section> <h2>

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:

js
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);
aluno = Marina Duarte email = marina@aurora.com.br curso = violao newsletter = on campos na tela: 7 campos enviados: 4

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:

js
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);
}
aurora-errado.html -> 0 rótulo(s): [] aurora.html -> 1 rótulo(s): [ 'Seu nome' ]

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 alt que 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 leva alt="" vazio, que é diferente de não ter alt.
  • 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-label no 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 &amp;nbsp; e &amp;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:

js
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));
bytes gravados no arquivo: 44 lido como UTF-8: Escola de Música Aurora — Violão e Canto lido como windows-1252: Escola de Música Aurora — Violão e Canto

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.

Ver a trilha
  1. 01O que é HTML e como o navegador transforma tag em página
  2. 02Estrutura de uma página HTML: doctype, html, head e body
  3. 03Tags e atributos HTML: anatomia de um elemento
  4. 04Meta tags no HTML: charset, viewport e o resto do head
  5. 05Títulos e parágrafos em HTML: h1 a h6, p, br e hr
  6. 06Formatar texto em HTML: negrito, itálico, código e citação
  7. 07Entidades HTML: &nbsp;, &amp; e os caracteres especiais
  8. 08Links em HTML: href, target, âncora e link de e-mail
  9. 09Imagens em HTML: img, alt, tamanho e carregamento lento
  10. 10Listas em HTML: ul, ol, li e lista de definição
  11. 11Tabelas em HTML: table, thead, tbody, colspan e rowspan
  12. 12Formulário em HTML: form, label e o que o navegador envia
  13. 13Tipos de input HTML: qual campo usar para cada dado
  14. 14Validação de formulário sem JavaScript: required e pattern
  15. 15HTML semântico: header, nav, main, section, article e footer
  16. 16Acessibilidade em HTML: alt, label, foco e atributos ARIA
  17. 17Vídeo e áudio em HTML: tag video, controls e autoplay
  18. 18iframe no HTML: incorporar YouTube, mapa e outra página
  19. 19SVG e canvas no HTML: qual usar para desenhar na página
  20. 20data attributes no HTML: guardar dado no próprio elemento
  21. 21Open Graph no HTML: o preview do link no WhatsApp

html

Perguntas frequentes

HTML é uma linguagem de programação?
Não. É uma linguagem de marcação: ela descreve o que cada pedaço do conteúdo é, e não tem variável, condição nem repetição. Quem programa a página é o JavaScript. Isso não a torna fácil — a parte difícil do HTML é escolher a tag certa, não decorar a sintaxe.
Quanto tempo leva para aprender HTML?
Para montar uma página completa sozinho, de duas a três semanas estudando uma hora por dia. O que continua evoluindo depois disso é o critério de escolha de tag e a acessibilidade, que amadurecem junto com os projetos.
Preciso decorar todas as tags?
Não existe isso. No censo de cinco páginas reais feito para este guia, vinte tags cobriram mais de 95% dos elementos. Decore essas vinte e consulte a MDN para o resto — é o que faz quem trabalha com isso todo dia.
Posso aprender HTML e CSS ao mesmo tempo?
Pode, e é até saudável depois da primeira semana. O que não funciona é começar pelo CSS: sem entender que a página é uma árvore de elementos, seletor e herança viram decoreba, e você não consegue explicar por que uma regra venceu a outra.
HTML5 é diferente de HTML?
Hoje é a mesma coisa. HTML5 foi o nome de uma virada de versão que trouxe as tags semânticas, os tipos novos de input e as tags de vídeo e áudio. A especificação atual é um documento vivo, sem número de versão.
Vale usar IA para escrever o HTML por mim?
Para gerar um rascunho, sim. Para aprender, não: o modelo acerta a estrutura e erra o critério — costuma entregar div para tudo e alt genérico. Peça a ele que explique por que escolheu cada tag, e revise.

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

  1. MDN Web Docs — HTML: linguagem de marcação de hipertexto — developer.mozilla.org
  2. HTML Living Standard — WHATWG — html.spec.whatwg.org
  3. html-validate — regras de validação de HTML — html-validate.org