Pular para o conteúdo
murad.guide

Introdução Completa à Web

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

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.

Estados :hover, :focus e ::before no cardápio da Casa da Restinga

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.

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

Início da referência desta etapa no navegador local

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

  1. Qual a diferença entre :hover e uma segunda página “botão laranja”?
  2. Por que este volume pede outline de cerca de 3px em :focus-visible?
  3. O que acontece se você omitir content em .nota::after?
  4. No exemplo, use só o teclado. O anel aparece no skip-link?

Respostas comentadas

  1. :hover é o mesmo elemento em outro estado. A segunda página seria outro arquivo, outro endereço, outro conteúdo.
  2. O anel fino some no fundo da página. Cerca de 3px, com cor contrastante, deixa o Tab visível.
  3. O pseudoelemento não é desenhado. content é o gatilho.
  4. Sim, se a regra :focus-visible estiver no skip-link. Se o anel não aparecer, outline: none ou 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); }