Desmitificando Seletores Complexos

Se existem recursos no CSS que a total compreensão se restringe a uma parcela de desenvolvedores, esses são os combinadores filhos (>), irmãos adjacentes (+) e adjacentes gerais (~).

Sem dúvidas, esses 3 combinadores são tão poderosíssimos quanto mal-explicados. É importante compreendê-los integralmente e há dois bons motivos para isso: o seletor descendente não dá conta de tudo e, o óbvio: o CSS está evoluindo.

X > Y

Todo filho é necessariamente descendente, mas nem todo descendente é necessariamente filho.

Eu, sobre família

Para que Y seja alvo da seleção, não importa a posição; basta que seja descendente direto de X - isso é, filho. Em outras palavras, basta que esteja interno diretamente ao elemento pai - e seja somente descendente dele. Isso quer dizer que Y não será alvo caso esteja interno a um elemento Z, mesmo que este esteja interno a X. Por essa razão o combinador “>” é também chamado de direto, pois não admite elementos internos indiretamente.

Seletor descendente vs. seletor filho

Lembrando da frase dita no início desse tópico, você já entende a diferença. Enquanto o descendente (X Y) herda as propriedades aos elementos direta e indiretamente internos (filhos, netos, bisnetos…), o alvo do combinador filho são os filhos unicamente diretos - sim, falar isso é redundante. O que faz todo sentido, afinal, um filho é tanto filho quanto descendente; e o neto, bisneto, trineto não é um filho, mas é descendente.

Na prática

Imaginemos um artigo e seus respectivos parágrafos. Dentro desse artigo, haverá uma seção de informações que não estará diretamente relacionada ao artigo. Como o que se quer destacar é a leitura do artigo, seus parágrafos terão mais ênfase de alguma forma.

HTML
<article>
  <h1>Título do artigo</h1>
  <p>Primeiro parágrafo</p>
  <p>Segundo parágrafo</p>
  <aside>
    <h2>Informações</h2>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
  </aside>
  <p>Terceiro parágrafo</p>
</article>
CSS
article > p {
  font-style: italic;
}

Assim, somente os parágrafos que são filhos diretos do elemento article serão estilizados.

X + Y

Se para ser alvo do seletor filho a posição era irrelevante, para ser alvo de um seletor irmão adjacente sua posição é critério decisivo. O elemento Y deve ser o primeiro elemento após X, com ambos dentro de um mesmo elemento Z (pai). O nome, portanto, é bem autoexplicativo: são irmãos por possuírem o mesmo pai (no caso, Z) e adjacentes por estarem necessariamente próximos.

Na prática 1

Supondo um artigo constituído por um título e 3 parágrafos. O primeiro parágrafo após o título servirá como uma introdução ao artigo e, portanto, deve ser destacado com um aumento no tamanho da fonte.

HTML
<article> <!-- Z -->
  <h1>Título do artigo</h1> <!-- X -->
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p> <!-- Y -->
  <p>Debitis sint aperiam numquam nisi animi porro in reprehenderit!</p>
  <p>Magnam atque placeat fuga sed eligendi maxime neque labore. Doloribus?</p>
</article>
CSS
h1 + p {
  font-size: 20px;
}

Na prática 2

O checkbox hack funciona com o uso do combinador irmão adjacente.

HTML

<input type="checkbox" id="hider">
<div>Hide me if you can!</div>
<label for="hider">Esconder div</label>

CSS

input[type="checkbox"] {
  display: none; /* Esconde o checkbox */
}
input:checked + div {
  display: none; /* Quando o checkbox for checado, a div será escondida */
}

X ~ Y

Seletor do CSS 3, o combinador adjacente geral tem uma definição bem semelhante ao irmão adjacente. Para que Y seja alvo, os elementos X e Y devem ser filhos de um mesmo elemento (irmãos) e X deve preceder Y, direta ou indiretamente - isso é, para que Y seja alvo, esse precedimento não precisa ser imediato.

Na prática

Esse combinador contorna algumas inflexibilidades do combinador irmão adjacente. Ainda com o exemplo do checkbox hack, podemos personalizar o elemento de forma não tão específica quanto à sua posição:

HTML

<input type="checkbox" id="shower">
<label for="shower">Mostrar div</label>
<div>Hide me if you can!</div>
<div>Hide me too if you can!</div>

CSS

input[type="checkbox"], div {
  display: none; /* Esconde o checkbox e a div, por padrão */
}
input:checked ~ div {
  display: block; /* Quando o checkbox for checado, a div aparecerá */
}

Conclusão

As linguagens CSS e HTML foram documentadas para serem intuitivas: os elementos formam famílias com outros elementos pais, filhos, descendentes, irmãos… Isso fica claro no nome dos seletores, que têm papel importante na compreensão do combinador; afinal, caso o desenvolvedor entenda que irmãos tem o mesmo pai e que filhos são descendentes diretos do pai, ele poderá tirar um bom proveito do nome dos combinadores para compreender seus funcionamentos.

[…] In both cases, non-element nodes (e.g. text between elements) are ignored when considering adjacency of elements.

W3C, sobre Selectors Level 3

Há uma grande confusão - e com razão - entre os seletores irmãos adjacentes e irmãos gerais. Essa confusão se origina não só de suas classificações como seletores de combinação, mas em seus comportamento e definição semelhantes. Tanto é verdade que o combinador adjacente geral (~) se comportará muitas vezes como um irmão adjacente (+), com a diferença de que o adjacente geral é menos exigente quanto à posição do elemento-alvo.

O uso será, portanto, facultativo em diversas situações. E, nesse caso, a minha recomendação é dar prioridade ao combinador adjacente, visto que é um seletor do CSS 2.1 e, portanto, compatível com uma maior gama de browsers. 😃

Referências