Já ouviu falar em CSS Condicional? Pois saiba que é possível usar CSS puro para verificar se o número de um elemento de grupo é menor ou igual a uma determinada quantidade — por exemplo, verificar se uma grid tem 3 ou mais itens.
Isso permite fazer coisas como: um componente ou layout pode mudar com base no número de elementos-filho.
Essa técnica poderosa de CSS é possível graças à pseudo-classe :has
, que pode ser combinada com o seletor :nth-last-child
para fazer a mágica acontecer.
Se você ainda desconhece ou não está familiarizado com :has()
, assista a nosso vídeo exclusivo:
Revisão de :nth-last-child
Um dos pontos-chave dessa técnica é entender que a pseudo-classe :nth-last-child
pode ser usada para simular a contagem de elementos-filho.
Considere o seguinte wireframe de uma lista com 5 cards:
No seguinte CSS, tem-se n + 3
:
1 2 3 |
li:nth-last-child(n + 3) { /* estilos */ } |
Que significa: selecione os três primeiros itens do final, contando a partir do terceiro item.
Analisando em mais detalhes, primeiro, é preciso contar 3 itens a partir do final; com isso, o 3º item é, na verdade, o 1º item que é contado até o final da lista.
Quando se conta do 3º até o final, estes são os itens selecionados:
Limitações de Quantity Queries em CSS
Conforme explicado no excelente artigo de Heydon Pickering, é possível usar o :nth-last-child
como uma Quantity Query (ou Consulta de Quantidade) — tema importante para CSS Condicional, como será mostrado a seguir.
Considere o seguinte planejamento visual para quando houver 3 informações e para quando houver mais do que 3 informações:
Há uma lista de informações que é exibida de forma diferente quando se tem 5 ou mais itens.
1 2 3 4 5 6 |
<ul> <li></li> <li></li> <li></li> <!-- mais itens --> </ul> |
1 2 3 4 5 6 7 8 9 10 11 |
li { /* estilos padrão */ } /* Se a lista tiver 5 ou mais itens */ li:nth-last-child(n + 5), li:nth-last-child(n + 5) ~ li { border-bottom: 0; display: inline-block; width: 50%; } |
Embora isso funcione, ainda é um pouco limitante em alguns aspectos.
Não é possível estilizar o elemento-pai com base no número de elementos
Imagine que seja preciso adicionar display: flex
a cada <li>
quando houver 5 ou mais itens. Não é possível fazer isso com somente :nth-last-child
.
O motivo é que adicionar display: flex
forçará cada item a ficar em sua própria linha, o que não corresponde ao planejamento visual a ser alcançado.
1 2 3 4 5 6 |
li:nth-last-child(n + 5), li:nth-last-child(n + 5) ~ li { display: flex; flex-direction: column; width: 50%; } |
É possível corrigir isso com display: inline-flex
, mas talvez ainda não seja a solução ideal. O motivo é que o navegador vai contabilizar o espaçamento entre os elementos HTML, que devem ficar assim:
1 2 3 4 |
<ul> <li></li><li></li><li></li> <!-- mais itens --> </ul> |
Se isso não for feito, display: inline-flex
terá o mesmo efeito que display: flex
. Um truque para corrigir isso é reduzir a largura em 1%:
1 2 3 4 5 6 |
li:nth-last-child(n + 5), li:nth-last-child(n + 5) ~ li { display: flex; flex-direction: column; width: 49%; } |
Funcionamento em diferentes tamanhos de viewport
Sem a capacidade de ter controle sobre o pai, não é tão simples estilizar o layout da listagem.
Por exemplo, quando a largura do container ou viewport é menor, é preciso mostrar 1 item por linha.
Mais trabalho para gerenciar o espaçamento
Quando há 3 itens ou menos, o espaçamento é horizontal, e quando são 5 ou mais, o espaçamento é vertical. É possível gerenciar isso manualmente invertendo a margem de horizontal para vertical ou usando a propriedade gap
com Flexbox. Mas, novamente, isso força a usar inline-flex
para esse caso.
A chave do sucesso
A pseudo-classe :nth-last-child
é a chave para construir layouts condicionais. Ao combiná-lo com :has
, é possível verificar se um elemento-pai possui, pelo menos, um número específico de itens e estilizá-lo de acordo.
As possibilidades são infinitas!
Casos de uso e exemplos de CSS Condicional com :has e :nth-last-child
Vamos a alguns exemplos e casos de uso CSS condicional com :has
e :nth-last-child
.
Grid que muda com base no número de elementos-filho
Quando é preciso alterar uma grid com base no número de itens, isso não é possível com o CSS atual. Na grid CSS, é possível usar a função minmax()
para ter uma grid dinâmica que muda com base no espaço disponível.
1 2 3 4 5 |
.list { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } |
O resultado será algo como:
O resultado não ficou ideal. Não é possível ter muito controle, já que é necessário ajustar o valor de 150px
no minmax()
. Pode funcionar muito bem quando tiver 4 itens ou menos e quebrar para 5 itens ou mais.
A solução? Verificar com :has
se há 5 itens ou mais e alterar o valor minmax()
com base nisso.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* grid padrão */ .list { --item-size: 200px; display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(var(--item-size), 1fr)); } /* Se a grid tem 5+ itens, muda --item-size para 150px */ .list:has(li:nth-last-child(n + 5)) { --item-size: 150px; } |
Perceba o uso de variáveis CSS, que facilitam a leitura do código, evitam duplicações e tornam manutenções futuras mais fáceis e eficientes.
Veja o vídeo a seguir e observe como as colunas da grid mudam conforme itens são adicionados ou removidos:
Cabeçalho com layout dinâmico
No wireframe a seguir, há um cabeçalho que deve mudar de layout caso seu menu tenha 4 ou mais itens.
Agora você já sabe que com as pseudo-classes CSS :has
e :nth-last-child
, é possível detectar isso e alterar o layout conforme seja necessário.
A mágica é feita usando esse CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.site-header:has(li:nth-last-child(n + 4)) { .site-header__wrapper > * { flex: initial; } .site-header__start { order: 2; } .site-header__middle { order: -1; text-align: start; } .site-header__end { margin-left: auto; } } |
Se estranhou essa sintaxe, assista a nosso vídeo sobre aninhamento nativo (nesting) de CSS:
Seria possível fazer um código melhor? Sim! Mas usando features de CSS que não são bem suportadas (ainda).
O macete seria adicionar uma variável CSS booleana que seria alternada quando o cabeçalho tiver 4 itens ou mais e, em seguida, usar uma style query para alterar o cabeçalho.
1 2 3 |
.site-header:has(li:nth-last-child(n + 4)) { --layout-2: true; } |
E, com isso, definir a variável –layout-2 quando houver 4 ou mais itens de navegação.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/* Só funcionará se a variável --layout-2 for definida */ @container style(--layout-2: true) { .site-header__wrapper { > * { flex: initial; } } .site-header__start { order: 2; } .site-header__middle { order: -1; text-align: start; } .site-header__end { margin-left: auto; } } |
É uma solução igualmente elegante e, para alguns, muito melhor do que aninhar todos os estilos CSS num seletor com :has
.
Veja a demo em ação:
Seção dinâmica de notícias
Esse é um wireframe de seção de notícias que deve mudar seu layout quando o número de itens for 3 ou mais.
Ao combinar CSS :has
e :nth-last-child
, torna-se possível criar uma variável CSS de alternância que será verificada por uma style query.
Assumindo que o estilo de card padrão é o horizontal:
1 2 3 4 5 |
<div class="layout"> <article class="card"></article> <article class="card"></article> <article class="card"></article> </div> |
1 2 3 4 5 6 7 8 9 10 |
.layout { display: grid; grid-gap: 1rem; } .card { align-items: center; display: flex; gap: 1rem; } |
Checando o número de elementos .card
:
1 2 3 4 |
.layout:has(.card:nth-last-child(n + 4)) { --layout-4: true; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); } |
A variável --layout-4
será alternada somente quando se tiver 4 itens ou mais. É possível verificar isso usando uma style query e atualizar .card
de acordo.
1 2 3 4 5 6 7 8 9 10 11 |
@container style(--layout-4: true) { .card { flex-direction: column; } .card__thumb { aspect-ratio: 4 / 3; flex: 1; width: 100%; } } |
Veja a demo em ação:
Botões de um modal
Neste exemplo, é preciso controlar dinamicamente o alinhamento dos botões de um modal com base em quantas ações é preciso mostrar.
Considere o seguinte:
Ou seja, quando houver 1 ação, o botão deve ser centralizado; caso contrário, os botões devem ficar alinhados à direita.
E aqui está o CSS:
1 2 3 4 5 6 7 8 9 10 11 |
.modal__footer { display: flex; gap: 0.5rem; justify-content: center; } /* Se houver 2 botões ou mais */ .modal__footer:has(a:nth-last-child(n + 2)) { justify-content: flex-end; } |
Achou simples, né? Então você já está pegando o jeito. :)
Veja a demo em ação:
Avatars
Neste exemplo de um projeto editorial, um artigo pode ser escrito por vários autores.
Um padrão comum é empilhar as imagens do autor com espaçamento negativo quando se tem vários autores.
Usando apenas quantity queries, é possível conseguir o mínimo, que é:
- Adicionar espaçamento negativo (“empilhar” os avatares);
- Reduzir o tamanho do avatar quando houver vários.
1 2 3 4 5 6 |
img:nth-last-child(n+2) ~ img { border: 2px solid #fff; height: 30px; margin-left: -0.25rem; width: 30px; } |
O CSS acima funciona, mas tem suas limitações. E se for preciso estilizar o próprio contêiner? Bem, é aí que o CSS :has
entra com o seu poder.
Primeiro, é preciso verificar e alternar uma variável CSS:
1 2 3 |
.post-author:has(img:nth-last-child(n + 2)) { --multiple-avatars: true; } |
Se essa variável CSS (simulando um booleano) for verdadeira, aplica-se os estilos para avatares múltiplos:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@container style(--multiple-avatars: true) { .avatars-list { background-color: #efefef; border-radius: 50px; display: flex; padding: 8px 12px; } img:not(:first-child) { border: solid 2px #fff; margin-left: -0.25rem; } } |
Veja a demo em ação:
Linha do tempo
Outro exemplo interessante em que o CSS Condicional funciona bem é um componente de linha do tempo.
Neste exemplo, a linha do tempo deve mudar de uma listagem vertical para um estilo alternado quando tiver 4 ou mais itens.
Primeiro, a técnica de usar :nth-last-child
com :has
:
1 2 3 |
.timeline-wrapper:has(.timeline__item:nth-last-child(n + 4)) { --alternating: true; } |
Se a condição for atendida, adiciona-se mais estilos, conforme a necessidade:
1 2 3 |
@container style(--alternating: true) { /* Estilos para a outra apresentação da linha do tempo */ } |
Usar style queries aqui torna possível reutilizar esse estilo em outra página, favorecendo a componentização:
1 2 3 |
.timeline-wrapper--page-10 { --alternating: true; } |
Código escrito só uma vez que funciona e pode ser usado em todo o projeto.
Veja a demo em ação:
Grid de logos
Uma das complicações de se lidar em CSS é alinhar vários logos e garantir que todos tenham uma boa aparência.
A técnica de CSS condicional torna possível, por exemplo, detectar o número de logos e reduzir um pouco o tamanho deles.
O seguinte CSS já dá conta do recado:
1 2 3 4 |
ul:has(li:nth-last-child(n + 8)) img { height: 35px; max-width: 160px; } |
Veja a demo em ação:
Conclusão
A combinação de recursos CSS modernos pode levar a novas maneiras empolgantes de criar layouts, e os exemplos deste artigo não foram exceção.
Como foi visto, alterar estilos com base no número de itens pode ser útil em muitos casos de uso diferentes. Além de ser muito legal. :)
Style Queries permitem escrever uma vez e reutilizar em qualquer lugar, favorecendo bastante a componentização.