O seletor que faltava ao CSS: :has()
O CSS sempre soube descer: escolher os filhos de um elemento, os irmãos a seguir, os descendentes. Nunca soube subir — não havia forma de dizer "o cartão que tem uma imagem lá dentro".
O :has() resolve isso, e está em todos os browsers atuais.
1. Ler ao contrário
/* Um cartão que tenha imagem fica com o texto centrado */
.cartao:has(img) {
text-align: center;
}
/* Um formulário com um campo inválido acende a moldura */
form:has(:invalid) {
border-color: crimson;
}
/* Uma lista sem itens nenhuns desaparece */
ul:not(:has(li)) {
display: none;
}O que o seletor escolhe é sempre o que está à esquerda do :has(). O que vai lá dentro é a condição.
2. Onde poupa mesmo JavaScript
O caso clássico é a etiqueta de um campo que reage ao estado dele. Antes, era um listener no change e uma classe posta à mão:
/* A etiqueta de uma opção escolhida */
label:has(input:checked) {
font-weight: 600;
background: #e8f5e9;
}
/* Um campo obrigatório por preencher, mas só depois de a pessoa lhe tocar */
label:has(input:required:invalid:not(:placeholder-shown)) {
color: crimson;
}3. Combinar com os irmãos
Dentro do :has() cabe qualquer seletor, incluindo os de irmãos — o que dá coisas que não tinham como se escrever:
/* Um título seguido imediatamente de uma imagem cola-se a ela */
h2:has(+ figure) {
margin-bottom: 0.25rem;
}
/* O artigo que contém o elemento em foco */
article:has(:focus-visible) {
outline: 2px solid;
}4. Uma nota de cautela
O :has() obriga o browser a reavaliar estilos de um antepassado quando um descendente muda. É rápido, mas não é grátis: usá-lo numa regra que apanhe muitos elementos e que dependa de estados que mudam a cada tecla é a forma de o notar. Para o resto — a esmagadora maioria dos casos — é o seletor que faltava há vinte anos.