:has(): o seletor de pai que faltava
Durante anos, CSS conseguia reagir ao que um elemento É, mas não ao que existe DENTRO dele. Um campo de formulário só ficava vermelho por causa de um input inválido lá dentro com a ajuda de JavaScript. Isso mudou.
Estudar esta lição interativa →Todo seletor de CSS, até aqui, descreve um elemento pelo que ele é (uma tag, uma classe, um estado) ou por quem está ao redor dele (o pai que o contém, um irmão). Nunca pelo que existe dentro dele. Durante anos, "se este elemento contém aquilo, estilize o próprio elemento" era só possível com JavaScript verificando o conteúdo e adicionando uma classe. A pseudoclasse :has() fecha essa lacuna: ela é, na prática, o "seletor de pai" que faltava no CSS.
Formulário com erro
.campo:has(input:invalid)Destaca o CONTÊINER do campo (label + input) quando o input dentro dele está inválido, sem precisar de JavaScript escutando o evento de validação.
Card com imagem
.card:has(img)Aplica um layout (ou espaçamento) diferente a um cartão SÓ quando ele contém uma imagem, útil quando o mesmo componente é reaproveitado com e sem foto.
🧩 Este conteúdo é interativo — abra no app para ver e rodar de verdade. .campo:has(input:invalid) destaca a borda do campo com erro; .card:has(img) dá um fundo diferente ao cartão que contém uma imagem.
Um exemplo de produto real: formulários de cadastro bem construídos destacam visualmente o campo (label + input + mensagem de ajuda, tudo dentro de um mesmo contêiner) quando o usuário digita algo inválido, sem esperar o clique em "Enviar". Antes de :has(), isso quase sempre exigia JavaScript escutando cada input e alternando uma classe manualmente; hoje, uma única regra CSS resolve, reagindo ao estado de validação nativo do próprio input.
Um cuidado importante: :invalid vale desde o carregamento da página. Um campo required vazio já nasce inválido, então o formulário abriria todo vermelho, antes de a pessoa tocar em qualquer coisa. É isso que você vê no exemplo acima, de propósito. Em um projeto de verdade, prefira .campo:has(input:user-invalid): o :user-invalid só passa a valer depois que a pessoa interagiu com o campo.
:has() passou a funcionar em todos os navegadores principais no fim de 2023. Em praticamente qualquer projeto atual isso já não é uma preocupação, mas vale saber: se algum dia você mexer num projeto realmente antigo, ou precisar suportar um navegador raro sem :has(), a regra simplesmente não se aplica, sem quebrar o resto da página, apenas sem o efeito extra.
🧩 Este conteúdo é interativo — abra no app para ver e rodar de verdade. Adicione .campo:has(input:invalid) com uma border-color de alerta.