HTML, CSS e JavaScript parecem três arquivos simples, mas no browser eles viram um sistema vivo. O HTML cria a árvore de elementos, o CSS calcula a apresentação, o JavaScript altera estado e comportamento, e o DOM é o contrato em memória onde tudo se encontra. Esta visão geral começa pelo encaixe básico e vai subindo até o pipeline real do browser, performance e arquitetura.
A separação clássica é simples e ainda é útil: HTML descreve o conteúdo, CSS descreve como esse conteúdo aparece, e JavaScript descreve o que acontece quando a página precisa reagir.
| Camada | Responsabilidade | Erro típico |
|---|---|---|
| HTML | Semântica, hierarquia, formulário, links, texto, mídia | Botão que deveria ser link, título fora de ordem, formulário sem label |
| CSS | Layout, cor, tipografia, estados, responsividade | Elemento desalinhado, quebra no mobile, regra sobrescrita sem querer |
| JavaScript | Eventos, estado, DOM, rede, fluxo assíncrono | Clique sem efeito, estado inconsistente, requisição fora de ordem |
Mas essa divisão não significa isolamento total. O CSS depende da estrutura do HTML. O JavaScript consulta e altera o DOM. Mudanças de classe feitas por JavaScript disparam novas regras CSS. Na prática, front-end é o estudo das fronteiras entre essas três camadas.
<button class="save-button" type="button">Salvar</button>
.save-button {
background: #2563eb;
color: #ffffff;
}
document.querySelector('.save-button')
.addEventListener('click', saveChanges);
HTML não é só marcação visual. Ele informa ao browser, leitores de tela, motores de busca e scripts o que cada parte da página significa. Um botão, um link e uma div clicável podem parecer iguais depois do CSS, mas não são equivalentes.
<article class="task-card" data-task-id="42">
<h2>Revisar pedido</h2>
<p>Pedido aguardando confirmação fiscal.</p>
<button type="button" aria-label="Marcar pedido como revisado">
Revisar
</button>
</article>
Esse trecho carrega várias decisões:
<article> diz que o card é uma unidade de conteúdo.<h2> preserva hierarquia navegável.<button> comunica ação, recebe foco e responde ao teclado.data-task-id cria um ponto de ligação entre DOM e lógica.aria-label melhora a descrição quando o texto visual não basta.Em páginas simples, semântica parece detalhe. Em aplicações grandes, ela vira contrato: testes automatizados encontram elementos, scripts ligam comportamento, acessibilidade funciona e o HTML continua legível mesmo antes do CSS carregar.
CSS parece uma lista de propriedades, mas o browser precisa resolver conflitos antes de desenhar qualquer coisa. A regra final aplicada a um elemento nasce da combinação de origem, importância, especificidade, ordem e herança.
button { color: #111827; }
.save-button { color: #2563eb; }
main .save-button { color: #16a34a; }
As três regras miram o mesmo botão. A última vence por ser mais específica. Esse é o tipo de decisão que explica por que "mudei o CSS e nada aconteceu" quase sempre é problema de cascade, seletor, ordem de carregamento ou estado.
Nem toda mudança visual custa a mesma coisa. Alterar width pode recalcular layout. Alterar color tende a repintar. Alterar transform ou opacity costuma ser mais barato porque pode ficar na etapa de composição.
| Mudança | Custo comum | Exemplo |
|---|---|---|
| Layout | Mais caro | width, height, display, font-size |
| Paint | Médio | color, background, box-shadow |
| Composite | Mais barato | transform, opacity |
JavaScript entra quando a página precisa decidir algo em runtime: responder a eventos, buscar dados, validar entrada, alterar estado e refletir esse estado no DOM. O ponto central é que o browser é orientado a eventos.
const state = {
selectedId: null,
loading: false
};
document.addEventListener('click', async function(event) {
const button = event.target.closest('[data-load-user]');
if (!button) return;
state.loading = true;
const response = await fetch(`/api/users/${button.dataset.loadUser}`);
const user = await response.json();
state.selectedId = user.id;
state.loading = false;
renderUser(user);
});
Esse exemplo mistura quatro coisas importantes: delegação de evento, atributo data-*, requisição assíncrona e renderização. Parece simples, mas em escala isso vira arquitetura.
JavaScript executa em uma thread principal, mas agenda trabalhos. Cliques, timers, promises e respostas de rede entram em filas. O event loop decide quando cada callback roda, intercalando código JS com renderização do browser.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// Ordem: A, D, C, B
Promises rodam antes de timers porque entram na fila de microtasks. Esse detalhe importa quando uma tela atualiza em ordem inesperada ou quando um loading pisca rápido demais.
Em código moderno, JavaScript tende a ser dividido em módulos. Isso evita variáveis globais e deixa dependências explícitas.
import { formatCurrency } from './money.js';
export function renderPrice(value) {
return formatCurrency(value, 'BRL');
}
O DOM é a representação viva do documento. O HTML inicial cria a primeira árvore, mas JavaScript pode consultar, criar, mover e remover nós. CSS observa essa árvore por seletores. O usuário interage com essa árvore por foco, clique, rolagem, seleção e teclado.
<ul id="tasks">
<li data-id="1">Compilar notas</li>
<li data-id="2">Revisar CSS</li>
</ul>
const list = document.querySelector('#tasks');
const item = document.createElement('li');
item.dataset.id = '3';
item.textContent = 'Publicar página';
list.appendChild(item);
Cada mutação pode ter custo. Em listas grandes, alterar o DOM item por item pode gerar trabalho repetido. Uma estratégia comum é montar um fragmento em memória e anexar tudo de uma vez.
const fragment = document.createDocumentFragment();
for (const task of tasks) {
const li = document.createElement('li');
li.textContent = task.title;
fragment.appendChild(li);
}
list.replaceChildren(fragment);
Frameworks como React, Vue e Svelte existem, em parte, para organizar esse problema: como transformar estado em DOM sem deixar a aplicação cheia de mutações manuais difíceis de rastrear.
Quando a página cresce, o modelo mental mais útil é o pipeline do browser. Ele mostra por que certas escolhas deixam a interface fluida e outras criam travamentos.
Por isso <script defer>, CSS crítico, imagens com tamanho definido e manipulação cuidadosa do DOM não são "otimização prematura"; são formas de não atrapalhar o pipeline.
<link rel="stylesheet" href="app.css">
<script src="app.js" defer></script>
| Problema | Causa comum | Correção típica |
|---|---|---|
| Tela pisca sem estilo | CSS tarde ou carregamento mal ordenado | Carregar CSS principal cedo |
| Layout salta | Imagem sem dimensão, fonte trocando, conteúdo tardio | Reservar espaço e definir dimensões |
| Interface trava | JS longo na main thread | Quebrar tarefa, adiar trabalho, reduzir DOM |
| Animação engasga | Animar propriedades que recalculam layout | Preferir transform e opacity |
Os próximos sub-hubs separam a tríade para estudar com profundidade, mas a leitura deve continuar conectada. HTML ruim força CSS frágil. CSS sem arquitetura vira guerra de especificidade. JavaScript sem modelo de estado vira sequência de remendos no DOM.
| Sub-hub | Primeira camada | Camada avançada |
|---|---|---|
| HTML | Tags, atributos, formulários | Semântica, acessibilidade, SEO técnico, contrato com JS |
| CSS | Seletores, box model, flex/grid | Cascade layers, arquitetura, tokens, performance de layout |
| JavaScript | Variáveis, funções, eventos | Event loop, módulos, async, estado, renderização |
A ordem pedagógica continua: primeiro entender a estrutura, depois controlar a apresentação, depois adicionar comportamento. Mas o objetivo final é enxergar a página como sistema: dados entram, estado muda, DOM reflete, CSS apresenta e o browser renderiza em frames.