Capítulo 9
Pseudoclasses e pseudoelementos
6 min de leitura 1.068 palavras 2 gravuras
Um botão da Casa da Restinga muda quando o ponteiro passa, quando o teclado foca, quando é o primeiro da lista. Isso não é outra página: é o mesmo elemento em outro estado. Este capítulo ensina pseudoclasses e pseudoelementos.
O exemplo está em exemplos/09-pseudo/. A Casa da Restinga é uma cafeteria fictícia em Natal. Nada aqui é cadastro real. Este volume tem 23 capítulos.
Trabalhe na cópia inicio da etapa. Leia e digite o código; mude uma coisa. Antes de recarregar, anote sua previsão. Depois confira com a referência completa. Se nada mudar, confira o arquivo salvo e o caminho aberto. Blocos adicionais do capítulo podem ser digitados no console; o exemplo completo mostra a aplicação daquela etapa.
O que você vai aprender
- Ligar um estilo a um estado com
:hovere:focus-visible. - Destacar o primeiro item com
:first-child. - Deixar o foco visível com contorno de cerca de 3px.
- Inserir um enfeite com
::beforee::after. - Distinguir
:(estado) de::(pedaço extra).
Estado não é outra página
Uma pseudoclasse é um seletor que só vale quando uma condição é verdadeira. O HTML continua o mesmo. O CSS reage ao ponteiro, ao teclado, à posição na lista.
O seletor a:hover lê: “um a, mas só enquanto o ponteiro está em cima”. Sem o :hover, a regra vale o tempo todo.
Abra exemplos/09-pseudo/index.html. O arquivo no disco não muda quando você passa o mouse. Só a página no navegador reage.
Mude uma coisa. Troque o texto do botão para Cardápio da Restinga. Preveja: o hover continua no mesmo elemento. Recarregue e confira.
Erro comum: criar uma segunda página só para o “botão pintado”. Estado é CSS, não arquivo novo.
:hover no cardápio
:hover vale enquanto o ponteiro está sobre o elemento. No telefone não há ponteiro estável: o hover some ou dispara ao toque. Por isso o visual principal não pode depender só dele.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hover — Casa da Restinga</title>
<style>
.prato {
width: 12rem;
padding: 1rem;
background: #0d5c63;
color: #fff;
}
.prato:hover {
background: #c45c26;
}
</style>
</head>
<body>
<p class="prato">Café coado da Restinga</p>
</body>
</html>
Mude uma coisa. No :hover, acrescente letter-spacing: 0.05em. Preveja um pouco mais de ar entre as letras. Recarregue e confira.
Erro comum: usar :hover como único aviso de link. Quem navega só com teclado não passa o mouse.

O primeiro da lista
:first-child só seleciona o elemento se ele for o primeiro filho do pai. Os irmãos com a mesma classe ficam de fora.
<ol>
<li class="item">Café coado</li>
<li class="item">Cappuccino de coco</li>
<li class="item">Pão de queijo de mandioca</li>
</ol>
<style>
.item { color: #585858; }
.item:first-child { color: #0d5c63; font-weight: bold; }
</style>
Há também :last-child (último) e :nth-child(2) (o segundo). Use com parcimônia: a ordem do HTML vira a regra.
Mude uma coisa. Troque :first-child por :last-child. Preveja o verde no pão de queijo. Recarregue e confira.
Erro comum: colocar um título h2 antes do primeiro li e achar que o li ainda é :first-child do ol. Ele continua sendo o primeiro filho da lista. O título está fora.
Foco visível de cerca de 3px
Quem usa Tab precisa ver onde está. :focus vale enquanto o elemento tem o foco. :focus-visible vale quando o navegador julga que o foco deve aparecer — em geral no teclado, não no clique do mouse.
Neste volume o contorno tem cerca de 3px, com cor da casa e um pequeno afastamento.
a:focus-visible,
button:focus-visible,
input:focus-visible {
outline: 3px solid #0d5c63;
outline-offset: 2px;
}
a:hover {
text-decoration: underline;
}
O skip-link do exemplo usa o mesmo contorno. Tab até ele. O retângulo deve ser nítido, não um fio de 1px.
Mude uma coisa. Troque 3px por 1px. Preveja um anel quase invisível. Recarregue, use Tab e confira. Depois volte para 3px.
Erro comum: outline: none em todos os estados. Sem foco visível, a página fecha a porta para o teclado.
Links visitados e o que não misturar
:visited pinta um link já aberto. O navegador limita o que essa regra pode mudar (em geral só cor), por privacidade.
a:link { color: #0d5c63; }
a:visited { color: #6b3f16; }
a:hover { color: #c45c26; }
a:focus-visible { outline: 3px solid #0d5c63; }
A ordem clássica é link, visited, hover, focus. Se o hover vier antes do visited, o visitado pode “ganhar” e o hover some.
Mude uma coisa. Inverta :hover e :visited no arquivo. Preveja o hover falhar no link já visto. Recarregue e confira. Depois restaure.
Erro comum: usar :visited para esconder preço ou conteúdo. Ele não é um login.
::before e ::after
Todo elemento tem dois pseudoelementos: ::before (antes do conteúdo) e ::after (depois). Eles não existem no HTML. O CSS os inventa.
Livros antigos punham um florão no fim do capítulo. Na Casa da Restinga, um enfeite fecha cada nota do blog.
<article class="nota">Maré baixa na Ponta Negra: café da manhã na varanda.</article>
<style>
.nota::after {
content: "❧";
display: block;
text-align: center;
font-size: 1.5rem;
color: #c45c26;
}
</style>
content é obrigatório. Sem ele, o pseudoelemento não aparece. O valor pode ser texto, aspas vazias "" (só para desenhar com CSS) ou uma imagem.
Código antigo usa :before com um dois-pontos. O padrão atual é ::before com dois pares, para não confundir com :hover.
Mude uma coisa. Troque ::after por ::before. Preveja o florão acima do texto. Recarregue e confira.
Erro comum: colocar HTML dentro de content. Só entra texto ou função CSS, não uma tag img.

Quando usar cada um
Pseudoclasse (:) = estado do elemento que já está no HTML.
Pseudoelemento (::) = pedaço extra que o CSS desenha.
Para o cardápio da Restinga: hover e foco são estados. O florão é enfeite. O preço, o nome do prato e o alt da foto continuam no HTML.
Teste em 127.0.0.1 se o servidor local estiver ligado. Arquivo no disco: as regras. Página no navegador: o estado. Publicação pública não é pedida.
Mude uma coisa. Apague só o content do ::after. Preveja o florão sumir. Recarregue e confira.
Erro comum: achar que ::after substitui um footer. Rodapé de página é HTML semântico.
Atividades
- Qual a diferença entre
:hovere uma segunda página “botão laranja”? - Por que este volume pede
outlinede cerca de 3px em:focus-visible? - O que acontece se você omitir
contentem.nota::after? - No exemplo, use só o teclado. O anel aparece no skip-link?
Respostas comentadas
:hoveré o mesmo elemento em outro estado. A segunda página seria outro arquivo, outro endereço, outro conteúdo.- O anel fino some no fundo da página. Cerca de 3px, com cor contrastante, deixa o Tab visível.
- O pseudoelemento não é desenhado.
contenté o gatilho. - Sim, se a regra
:focus-visibleestiver no skip-link. Se o anel não aparecer,outline: noneou a ordem da cascata apagou o foco.
Referências
O arquivo completo
Referência do exemplo 09-pseudo, reproduzida aqui para que você possa conferir sem sair da página.
assets/stylesheets/main.css
*,
*::before,
*::after { box-sizing: border-box; }
html, body, h1, h2, h3, p, ul { margin: 0; padding: 0; }
img { max-width: 100%; height: auto; }
body {
font-family: "Segoe UI", Tahoma, sans-serif;
line-height: 1.5;
color: #1a2a2c;
background: #f7f3ea;
}
a { color: #0d5c63; }
a:focus-visible, button:focus-visible, input:focus-visible {
outline: 3px solid #c45c26;
outline-offset: 3px;
}
.skip { position: absolute; left: -999px; }
.skip:focus { left: 0.5rem; top: 0.5rem; background: #fff; padding: 0.4rem 0.7rem; z-index: 2; }
.wrap { width: min(44rem, calc(100% - 2rem)); margin-inline: auto; }
header { background: #0d5c63; color: #fff; padding: 1rem 0; }
header a { color: #fff; }
.brand { font-weight: 700; text-decoration: none; }
nav ul { list-style: none; display: flex; gap: 1rem; margin-top: 0.5rem; }
nav a[aria-current="page"] { text-decoration: underline; text-underline-offset: 0.2em; }
main { padding: 1.5rem 0 3rem; }
footer { border-top: 1px solid #ddd4c4; padding: 1rem 0; font-size: 0.95rem; }
article { margin: 1.25rem 0; padding-bottom: 1rem; border-bottom: 1px solid #ddd4c4; }
a:hover { text-decoration: underline; }
.aviso::before { content: "Aviso: "; font-weight: 700; color: #c45c26; }
button { background: #0d5c63; color: #fff; border: 0; padding: 0.5rem 1rem; border-radius: 6px; }
button:hover { filter: brightness(1.08); }