< Voltar

sibling-index() e sibling-count(): o novo CSS que elimina muitas linhas de JavaScript

Novos recursos quase revolucionários no CSS, sibling-index() e sibling-count() chegaram para ajudar a separar responsabilidades no front-end...

sibling-index() e sibling-count(): o CSS que vai mudar como você pensa em loops

Novos recursos quase revolucionários no CSS, sibling-index() e sibling-count() chegaram para ajudar a separar responsabilidades no front-end.

antes: animar cada item de uma lista com delay sequencial? JavaScript. Distribuir larguras dinamicamente baseadas no número de filhos? JavaScript. Gerar cores baseadas na posição de um elemento? JavaScript. agora: o CSS spec ganhou duas funções que atacam diretamente esse problema: sibling-index() e sibling-count(). E elas são, sem exagero, quase revolucionárias.

O que são

sibling-index() retorna o índice do elemento dentro do seu pai — começando em 1. sibling-count() retorna o número total de irmãos, incluindo o próprio elemento.

Simples assim. Mas as implicações são enormes.

li { /* cada li sabe sua posição e o total */ --i: sibling-index(); --total: sibling-count(); }

Antes dessas funções, obter esse tipo de informação no CSS exigia hacks com :nth-child() hardcoded, variáveis CSS setadas no HTML inline, ou JavaScript injetando style="--i: 3" em cada elemento. Nenhuma dessas abordagens é elegante. Todas quebram quando o conteúdo é dinâmico.

Animações fade-in sequenciais sem uma linha de JS

O caso de uso mais imediato e satisfatório. Aquele efeito de lista que aparece item por item, cada um com um delay ligeiramente maior que o anterior.

Antes (com JS)

document.querySelectorAll('.item').forEach((el, i) => { el.style.animationDelay = `${i * 0.1}s`; });

Agora (CSS puro)

@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .lista li { animation: fadeUp 0.5s ease forwards; animation-delay: calc((sibling-index() - 1) * 0.12s); opacity: 0; }
<ul class="lista"> <li>Item um</li> <li>Item dois</li> <li>Item três</li> <li>Item quatro</li> <li>Item cinco</li> </ul>

Cada <li> automaticamente calcula seu próprio delay. Adicione ou remova itens no HTML — o CSS se adapta. Sem recount, sem loop, sem evento DOMContentLoaded.

Variação: stagger reverso

.lista li { animation: fadeUp 0.5s ease forwards; /* o último aparece primeiro */ animation-delay: calc((sibling-count() - sibling-index()) * 0.1s); opacity: 0; }

Variação: fade-in em grid

.grid-cards .card { animation: fadeUp 0.4s ease forwards; animation-delay: calc(sibling-index() * 0.08s); opacity: 0; }

Zero JavaScript. Zero atributos inline no HTML. O markup permanece limpo.

Cores dinâmicas com rgb() e hsl()

Essa é onde a coisa fica realmente interessante. Combinar sibling-index() com funções de cor abre possibilidades que antes exigiam preprocessadores ou scripts.

Com rgb() — mais bruto, mais interessante

.item { --n: sibling-index(); background: rgb( calc(var(--n) * 30), calc(255 - var(--n) * 20), calc(var(--n) * 15) ); }

Aqui cada canal RGB é influenciado pelo índice do elemento. O resultado não é exatamente "aleatório" — é determinístico e baseado na posição — mas visualmente parece uma paleta rica e variada.

Opacidade progressiva

.degrade-list li { opacity: calc(sibling-index() / sibling-count()); }

O primeiro item fica quase invisível, o último totalmente opaco. Um efeito de fade que antes precisava de CSS gerado ou JS, agora em duas linhas.

Distribuição automática de largura com sibling-count()

Um dos problemas mais clássicos do CSS: você tem uma navegação horizontal e quer que cada item ocupe exatamente 100% / n do espaço disponível — onde n é o número de itens. Antes, isso exigia JS ou um número fixo hardcoded no CSS.

nav ul li { width: calc(100% / sibling-count()); }

É isso. Adicione um item no HTML, o CSS redistribui automaticamente. Remova um item, idem.

Usos criativos com content

sibling-index() pode ser usado com pseudo-elementos para gerar numeração automática — mas de forma mais flexível que counter().

Numeração automática sem counter

.steps li::before { content: counter(step); counter-increment: step; }

Isso já existia. Mas com sibling-index() você pode fazer mais:

.steps li::before { content: "0" counter(step); counter-increment: step; /* e também usar sibling-index() para o estilo */ background: hsl(calc(sibling-index() * 40) 80% 50%); width: 2rem; height: 2rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; }

Número e cor, ambos automáticos baseados na posição.

Por que isso é (quase) revolucionário

A grande revolução não é nenhum caso de uso específico. É o princípio.

Pela primeira vez, o CSS tem acesso a informações estruturais do DOM de forma nativa. Um elemento pode saber sua posição relativa entre irmãos sem que essa informação precise ser explicitamente injetada — seja via atributo HTML, variável inline, ou JavaScript.

Isso fecha um gap que existia desde sempre. CSS sempre foi declarativo, mas sempre dependeu de dados externos para casos dinâmicos. sibling-index() e sibling-count() tornam o CSS genuinamente autoconsciente da estrutura que está estilizando.

O impacto prático:

  • Menos JavaScript de manipulação de DOM para estilo puro
  • HTML mais limpo — sem data-index ou style="--i: 3" espalhados
  • CSS mais resiliente — adicione ou remova elementos sem quebrar o estilo
  • Componentes verdadeiramente encapsulados — o CSS do componente funciona independente de quantos filhos ele tem

Market share e suporte: por que você pode usar agora

Aqui entra um dado que poucos desenvolvedores brasileiros consideram: o Firefox tem market share baixíssimo no Brasil. Para os ~2-3% de usuários Firefox, a estratégia de fallback é simples — as propriedades simplesmente são ignoradas e o layout cai para o comportamento padrão sem as melhorias progressivas:

Compatibilidade: Chrome 117+, Edge 117+, Safari 17.2+ — cobre ~97% dos usuários brasileiros.
Referências: MDN sibling-index(), StatCounter Brazil