Seu ciclo de foco terminou. Descanse um pouco.
Do primeiro <div> às condicionais nativas do CSS
moderno — uma apostila de engenharia frontend para quem quer entender
a web na raiz, não apenas decorar sintaxe.
Toque em qualquer item para ir direto à página. Os módulos seguem uma progressão lógica — do fundamento à fronteira da especificação.
if(), :has(), nesting nativo e
@scope
Uma trilha sonora ambiente combinada com um temporizador Pomodoro — ferramenta ideal para manter o foco durante seus estudos.
🎯 25 min de foco, 5 min de pausa. A cada 4 ciclos, uma pausa longa de 15 min.
A técnica Pomodoro alterna períodos de foco intenso com pausas curtas, mantendo a mente fresca por mais tempo. Use a música lo-fi como pano de fundo para entrar no estado de fluxo.
HTML (HyperText Markup Language) não é uma linguagem de programação — é uma linguagem de marcação. Sua função é descrever a estrutura e o significado do conteúdo, não seu comportamento. Entender essa distinção é o primeiro passo para escrever HTML que funciona bem para humanos, motores de busca e tecnologias assistivas ao mesmo tempo.
Todo documento HTML válido segue um esqueleto mínimo. Cada peça desse esqueleto tem uma função específica que impacta renderização, SEO e acessibilidade — nada ali é decorativo.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minha Página</title>
<meta name="description" content="Resumo objetivo da página para SEO.">
</head>
<body>
<h1>Olá, mundo</h1>
</body>
</html>
| Item | Por que existe |
|---|---|
<!DOCTYPE html> |
Ativa o "modo standards" do navegador. Sem ele, o browser entra em quirks mode e recalcula box model e layout de forma inconsistente. |
lang="pt-BR" |
Informa idioma a leitores de tela (pronúncia correta) e a motores de busca. Impacta diretamente a acessibilidade. |
meta charset |
Define a codificação de caracteres. Deve ser a primeira coisa
dentro de <head> — evita "mojibake" (acentos
quebrados).
|
meta viewport |
Sem essa linha, navegadores mobile renderizam a página como se fosse desktop e depois reduzem — resultado: texto ilegível sem zoom manual. |
O DOCTYPE e o charset parecem burocracia, mas são a diferença entre um layout previsível e horas de debugging por causa de um "quirks mode" silencioso.
Antes de qualquer framework, HTML puro já oferece um vocabulário rico para estruturar texto. A regra de ouro: escolha a tag pelo significado, não pela aparência visual — aparência é responsabilidade do CSS.
<article>
<h2>Título do artigo</h2>
<p>Parágrafo com <strong>ênfase forte</strong> e <em>ênfase leve</em>.</p>
<ul>
<li>Item sem ordem específica</li>
<li>Outro item</li>
</ul>
<ol>
<li>Primeiro passo</li>
<li>Segundo passo</li>
</ol>
<blockquote cite="https://exemplo.com">
Uma citação de outra fonte.
</blockquote>
</article>
Erro comum: usar <b> e
<i> pensando em "negrito" e "itálico" puramente
visuais. Prefira <strong> (importância semântica)
e <em> (ênfase de leitura) — leitores de tela
alteram entonação com base neles, e o CSS assume a aparência quando
necessário.
HTML semântico é o que transforma uma "sopa de divs" em um documento que uma tecnologia assistiva, um crawler de busca ou outro desenvolvedor conseguem interpretar sem ambiguidade.
Elementos como <header>,
<nav>, <main>,
<aside> e <footer> criam
landmarks — pontos de navegação que leitores de
tela expõem para o usuário pular direto ao conteúdo relevante, sem
precisar ouvir o menu inteiro toda vez.
<body>
<header>
<nav aria-label="Navegação principal">
<ul>
<li><a href="/">Início</a></li>
<li><a href="/blog">Blog</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Título da página</h1>
<section aria-labelledby="intro-h">
<h2 id="intro-h">Introdução</h2>
<p>Conteúdo...</p>
</section>
</article>
<aside aria-label="Conteúdo relacionado">
<p>Links relacionados</p>
</aside>
</main>
<footer>
<p>© 2026 OJuanDev</p>
</footer>
</body>
Regra prática: use
<section> apenas quando o bloco tiver um heading
próprio e representar um agrupamento temático real. Se você só
precisa de um gancho para estilizar, use <div> —
ela é semanticamente neutra e correta para esse uso.
A primeira regra do ARIA (Accessible Rich Internet Applications) é:
não use ARIA se um elemento HTML nativo já resolve o problema. Um <button> já tem foco, papel semântico e
ativação por teclado embutidos — recriar isso com
<div role="button"> exige reimplementar tudo
manualmente e é fonte constante de bugs de acessibilidade.
<!-- Evite -->
<div role="button" tabindex="0"
onclick="salvar()">
Salvar
</div>
<!-- Prefira -->
<button type="button"
onclick="salvar()">
Salvar
</button>
Use ARIA para os casos que o HTML puro não cobre: estados dinâmicos
(aria-expanded, aria-live), relações entre
elementos (aria-describedby) e componentes complexos
sem equivalente nativo (tabs, comboboxes customizados).
Todo elemento interativo precisa de um
estado de foco visível. Nunca use
outline: none; sem fornecer uma alternativa visual —
isso remove a navegação por teclado para uma parcela real de
usuários.
Formulários são a superfície de interação mais comum da web — e também a mais mal implementada. HTML5 trouxe tipos de input e validação nativa que eliminam boa parte do JavaScript que antes era necessário.
<form action="/cadastro" method="post" novalidate>
<label for="email">E-mail</label>
<input type="email" id="email" name="email" required
autocomplete="email">
<label for="idade">Idade</label>
<input type="number" id="idade" name="idade"
min="18" max="120" step="1">
<label for="site">Site</label>
<input type="url" id="site" name="site"
pattern="https://.*">
<button type="submit">Enviar</button>
</form>
Tipos como email, tel, date e
number não são só sintáticos: eles alteram o teclado
exibido em dispositivos móveis e ativam validação de formato pelo
próprio navegador, sem uma linha de JavaScript.
O atributo for em <label> deve
corresponder exatamente ao id do input. Isso amplia a
área clicável e é obrigatório para leitores de tela anunciarem o
campo corretamente.
O elemento <picture> permite servir formatos e
tamanhos de imagem diferentes conforme a viewport ou o suporte do
navegador — sem depender de JavaScript.
<picture>
<source srcset="banner.avif" type="image/avif">
<source srcset="banner.webp" type="image/webp">
<img src="banner.jpg" alt="Skyline da cidade ao entardecer"
width="1200" height="600" loading="lazy">
</picture>
<video controls preload="metadata" poster="capa.jpg">
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="legendas-pt.vtt" srclang="pt" label="Português">
</video>
O atributo width e height no
<img> continua essencial mesmo em layouts
fluidos: ele permite ao navegador reservar o espaço antes da imagem
carregar, evitando Cumulative Layout Shift — uma
métrica central de performance (Core Web Vitals).
CSS não é uma lista de propriedades a decorar — é um sistema de regras em cascata com precedência bem definida. Entender como o navegador decide qual regra vence evita 90% dos "!important" desnecessários.
Cada seletor tem um "peso" calculado em três categorias: IDs, classes/atributos/pseudo-classes, e elementos/pseudo-elementos. O navegador soma esses pesos para decidir qual regra prevalece em caso de conflito.
/* especificidade (0,0,1) — 1 elemento */
p { color: white; }
/* especificidade (0,1,0) — 1 classe */
.destaque { color: #c8f050; }
/* especificidade (0,1,1) — 1 classe + 1 elemento */
p.destaque { color: orange; }
/* especificidade (1,0,0) — 1 ID, sempre vence as anteriores */
#titulo { color: red; }
/* combinadores */
.card > h3 { } /* filho direto */
.card + .card { } /* irmão adjacente */
.card ~ p { } /* irmão geral */
a:not(.externo) { } /* negação */
| Seletor | Peso | Quando usar |
|---|---|---|
* |
0,0,0 | Resets globais |
.classe |
0,1,0 | Padrão recomendado para estilização de componentes |
[type="text"] |
0,1,0 | Estilizar por atributo/estado |
#id |
1,0,0 | Evitar em CSS de componente — dificulta sobrescrita |
style="" inline |
vence tudo, exceto !important |
Evitar; quebra a separação de responsabilidades |
Todo elemento é uma caixa retangular composta por
content, padding,
border e margin. A propriedade
box-sizing decide se width/height
se referem só ao conteúdo (content-box, padrão
problemático) ou incluem padding e border (border-box,
o que a maioria dos sistemas de design usa).
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100dvh; /* dvh = dynamic viewport height,
resolve o bug da barra de endereço
em navegadores mobile */
}
img, picture, video {
max-width: 100%;
display: block;
}
margin colapsa entre elementos irmãos em fluxo normal
(o maior valor "vence"), mas padding nunca colapsa.
Isso explica por que espaçamentos verticais às vezes parecem
"menores que o esperado".
Flexbox resolve o alinhamento e a distribuição de espaço em uma dimensão por vez (linha ou coluna). É a ferramenta certa para barras de navegação, cards em linha, ou centralizar qualquer coisa.
.container {
display: flex;
flex-direction: row; /* define o eixo principal */
justify-content: space-between; /* alinhamento no eixo principal */
align-items: center; /* alinhamento no eixo transversal */
gap: 1rem; /* espaçamento sem margin hacks */
flex-wrap: wrap;
}
.item-flexivel {
flex: 1 1 240px;
/* grow | shrink | basis
"cresça, encolha, mas comece com 240px" */
}
.centralizar-tudo {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
| Propriedade | Atua em | Efeito |
|---|---|---|
justify-content |
eixo principal | Distribui espaço entre os itens |
align-items |
eixo transversal | Alinha itens em uma linha |
align-content |
eixo transversal | Alinha múltiplas linhas quando há flex-wrap |
flex-grow |
item | Quanto o item cresce para preencher espaço livre |
Regra prática de decisão: se você está distribuindo itens em uma única linha ou coluna, use Flexbox. Se precisa controlar linhas e colunas simultaneamente, vá para Grid — módulo a seguir.
Grid controla linhas e colunas simultaneamente, permitindo posicionar elementos em um sistema de coordenadas explícito — o layout deixa de ser uma sequência de hacks e passa a ser declarado.
.pagina {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
min-height: 100vh;
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* grade de cards responsiva sem media query */
.galeria {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.5rem;
}
A combinação repeat(auto-fit, minmax(220px, 1fr)) é um
dos padrões mais úteis do CSS moderno: cria quantas colunas
couberem, cada uma com no mínimo 220px, e distribui o espaço
restante — tudo sem uma única @media.
Pense em Flexbox como conteúdo definindo o layout (os itens ditam o espaço) e Grid como layout definindo onde o conteúdo entra (você desenha a malha primeiro). Muitos projetos reais usam os dois juntos: Grid na macroestrutura da página, Flexbox dentro dos componentes.
.card-lista {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
/* o card herda as linhas da grade-pai, então título,
descrição e botão alinham entre todos os cards
mesmo com textos de tamanhos diferentes */
}
Media queries respondem ao tamanho da viewport. Container queries respondem ao tamanho do elemento pai — uma mudança de paradigma que torna componentes verdadeiramente reutilizáveis, independente de onde forem colocados na página.
/* mobile-first: estilos base = mobile,
media queries adicionam complexidade para telas maiores */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1200px) {
.grid { grid-template-columns: repeat(4, 1fr); }
}
@media (prefers-color-scheme: dark) {
:root { --bg: #080808; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important; }
}
Um card dentro de uma sidebar estreita precisa se comportar diferente do mesmo card dentro de uma área principal larga — mesmo na mesma viewport. Isso é impossível com media queries puras, e é exatamente o que container queries resolvem.
.card-wrapper {
container-type: inline-size;
container-name: card;
}
.card {
display: flex;
flex-direction: column;
}
@container card (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
}
}
/* unidades relativas ao container, não à viewport */
.card h3 {
font-size: clamp(1rem, 5cqi, 1.6rem);
}
Container Queries têm suporte estável em todos os navegadores
modernos desde 2023. Para o componente responder ao próprio
tamanho, o elemento pai precisa declarar
container-type — o filho não pode consultar a si
mesmo.
CSS que funciona em um protótipo de 200 linhas costuma desmoronar em um produto de 20 mil linhas. Arquitetura CSS é o conjunto de convenções que mantêm o código previsível conforme o projeto cresce.
:root {
--cor-bg: #080808;
--cor-texto: #ffffff;
--cor-destaque: #c8f050;
--espaco-1: 0.25rem;
--espaco-2: 0.5rem;
--espaco-4: 1rem;
--espaco-8: 2rem;
--raio-padrao: 10px;
--transicao-padrao: 200ms ease;
}
.botao {
background: var(--cor-destaque);
padding: var(--espaco-2) var(--espaco-4);
border-radius: var(--raio-padrao);
transition: transform var(--transicao-padrao);
}
.botao:hover { transform: translateY(-2px); }
Custom Properties não são só "variáveis SASS que rodam no navegador": elas são valores vivos, herdados em cascata, que podem mudar em tempo real com JavaScript ou dentro de uma media query, sem recompilar nada.
@layer permite declarar explicitamente a ordem de
prioridade entre grupos de estilos — resets, biblioteca de
componentes, utilitários — sem depender de especificidade ou da
ordem de importação no arquivo. Uma camada declarada depois tem
prioridade sobre uma camada anterior,
independente da especificidade dos seletores dentro dela.
@layer reset, base, componentes, utilitarios;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer componentes {
.botao { background: var(--cor-destaque); padding: 1rem; }
}
@layer utilitarios {
/* mesmo com especificidade menor, .oculto vence .botao
porque a camada "utilitarios" foi declarada por último */
.oculto { display: none; }
}
Antes das Cascade Layers, misturar um reset, uma biblioteca de
terceiros e utilitários próprios frequentemente virava uma guerra
de !important. Com layers, a prioridade é declarada
uma única vez, no topo do arquivo, de forma legível.
BEM (Block, Element, Modifier) evita colisão de nomes e torna a relação entre elementos explícita só pelo nome da classe — sem precisar navegar pelo HTML para entender a hierarquia.
<div class="card card--destaque">
<h3 class="card__titulo">Título</h3>
<p class="card__descricao">Texto</p>
<button class="card__botao card__botao--primario">Ação</button>
</div>
bloco → bloco__elemento →
bloco__elemento--modificador. Isso mantém a
especificidade sempre plana (uma classe = um seletor), o que casa
perfeitamente com Cascade Layers.
Estes são os recursos que estão redesenhando o que é possível fazer sem JavaScript. Alguns já têm suporte amplo, outros ainda estão em estágio experimental — todos representam a direção para onde o CSS está indo.
A função condicional if() traz lógica condicional
direto ao valor de uma propriedade, avaliando o estado de uma Custom
Property, uma media feature ou uma container query — sem duplicar
blocos de regras inteiros.
.painel {
--theme: dark;
background: if(
style(--theme: dark): #0f172a;
style(--theme: light): #ffffff;
else: #64748b
);
color: if(
style(--theme: dark): #f1f5f9;
else: #0f172a
);
}
Trata-se de um recurso de ponta, ainda em processo de adoção pelos
navegadores. Use com progressive enhancement: forneça um
valor padrão fora do if() e trate-o como
aprimoramento, nunca como base de suporte.
Por décadas, CSS não tinha como selecionar um elemento com base no
que existe dentro dele. :has() resolve isso e
permite lógica condicional puramente estrutural, sem JavaScript.
/* estiliza o card SOMENTE se ele contiver uma imagem */
.card:has(img) {
grid-template-rows: auto 1fr;
}
/* formulário com campo inválido: destaca o wrapper inteiro */
.form-group:has(input:invalid) {
border-color: #ff5f56;
}
/* CSS Nesting nativo — sem pré-processador */
.card {
padding: 1rem;
border: 1px solid var(--line);
& h3 {
color: var(--accent);
}
&:hover {
border-color: var(--accent);
}
& .card__botao {
&:disabled { opacity: .5; }
}
}
.titulo {
/* fluido entre 1.5rem e 3rem, escalando com a viewport */
font-size: clamp(1.5rem, 1rem + 2.5vw, 3rem);
}
.coluna {
width: min(90%, 1200px);
}
.espacamento {
padding: max(1rem, 4vw);
}
.grade-dinamica {
/* round(), mod() e pow() permitem cálculos antes
impossíveis sem pré-processador */
--colunas: round(down, 100% / 220px, 1);
grid-template-columns: repeat(var(--colunas), 1fr);
}
@scope limita onde uma regra CSS se aplica, sem
depender de convenções de nomenclatura como BEM. Você define um
limite superior (donor) e, opcionalmente, um limite
inferior (limit) onde a regra para de valer.
@scope (.artigo) to (.artigo-embutido) {
p { line-height: 1.8; }
a { color: var(--accent); }
}
/* essas regras NUNCA vazam para fora de .artigo,
nem afetam um .artigo aninhado dentro de outro (.artigo-embutido) */
@scope é especialmente útil ao integrar HTML de
terceiros (conteúdo de CMS, widgets embutidos) sem risco de
colisão de estilos, e como alternativa nativa a soluções de
CSS-in-JS focadas em isolamento.
Código correto não é o mesmo que código pronto para produção. Este módulo cobre o que separa um site que "funciona na sua máquina" de um site rápido, indexável e resiliente.
| Métrica | O que mede | Alavanca de CSS/HTML |
|---|---|---|
| LCP | Tempo até o maior elemento visível renderizar |
Evitar CSS bloqueante grande; usar
font-display: swap; pré-carregar imagem hero com
<link rel="preload">
|
| CLS | Quanto o layout "pula" durante o carregamento |
Sempre declarar width/height em mídia;
reservar espaço para anúncios e fontes web
|
| INP | Responsividade a interações do usuário | Evitar seletores CSS excessivamente complexos e reflows custosos disparados por JS |
<h1> por página, com hierarquia de
headings sem "pular níveis" (de h2 direto para
h4, por exemplo).
<title> único e descritivo por página — é o
texto mais pesado para ranqueamento e para a taxa de cliques nos
resultados de busca.
alt) descritivo em imagens com
significado; alt="" vazio (não ausente) em imagens
puramente decorativas.
<script type="application/ld+json"> para rich
snippets (avaliações, receitas, eventos).
<link rel="canonical"> para
evitar conteúdo duplicado.
<!-- critico: inline no head, o resto carrega assíncrono -->
<style>
/* CSS crítico do above-the-fold, minificado */
</style>
<link rel="preload" href="fontes.woff2" as="font"
type="font/woff2" crossorigin>
<link rel="stylesheet" href="resto.css" media="print"
onload="this.media='all'">
Evite seletores universais profundamente aninhados (div div div span) — o navegador avalia seletores da direita para a esquerda, e
cadeias longas custam tempo de recálculo de estilo em páginas
grandes. Prefira classes diretas.
prefers-reduced-motion respeitado em toda animação
não essencial.
alt, dimensões explícitas e
loading="lazy" fora do viewport inicial.
@layer) ou metodologia
consistente (BEM/utilitários).
| Categoria | Ferramenta | Uso |
|---|---|---|
| Validação | W3C Markup Validator | Checar erros estruturais de HTML |
| Acessibilidade | axe DevTools | Auditoria automatizada de a11y no navegador |
| Performance | Lighthouse / PageSpeed Insights | Medir Core Web Vitals |
| Suporte de navegador | Can I Use |
Checar compatibilidade de recursos como @scope e
if()
|
| Design system | Custom Properties + Cascade Layers | Tokens de tema sem dependência de pré-processador |
HTML e CSS evoluem rápido, mas os fundamentos deste material — cascata, especificidade, semântica, box model — não mudam. Domine a base; os recursos de ponta se tornam triviais de aprender quando a fundação está sólida.
Termos técnicos usados ao longo da apostila, para consulta rápida sem precisar voltar ao módulo original.
@layer) com
prioridade explícita entre grupos.
--nome), herdada em cascata e
alterável em tempo real.
Essencial: HTML & CSS
By OJuanDev — Escrito para quem constrói a web na raiz. Use ← para voltar ao início.