Kala Tech

ΚΑΛΑ • GOOD TECH

Mastering the CSS sibling-index() Function: Dynamic Indexing Without JavaScript

For decades, one of CSS's most frustrating limitations was styling elements based on their position in a list without hardcoding rules.

If you wanted staggered animation delays or dynamic color gradients across a set of child elements, your choices were limited:

  1. Write dozens of repetitive :nth-child(1), :nth-child(2), :nth-child(3) declarations.
  2. Generate them with a CSS preprocessor like Sass using @for $i from 1 through 20.
  3. Inject inline custom properties like style="--i: 0" through JavaScript or server-side templates.

With the CSS Values and Units Module Level 5, CSS introduces native solutions to this long-standing challenge: sibling-index() and its companion sibling-count().


1. What is sibling-index()?

The sibling-index() function returns an <integer> representing the position of the current element among its sibling elements under the same parent.

.item {
  animation-delay: calc(sibling-index() * 100ms);
}

Core Characteristics:


2. Meet Its Partner: sibling-count()

Along with sibling-index(), the specification introduces sibling-count(), which returns an <integer> representing the total number of sibling elements within the parent (including the element itself).

.card {
  /* Distribute items equally across total siblings */
  width: calc(100% / sibling-count());
}

Together, sibling-index() and sibling-count() allow relative calculations:

$$\text{Progress} = \frac{\text{sibling-index}()}{\text{sibling-count}()}$$

This ratio gives you the exact normalized position ($0.0 \dots 1.0$) of every element across any dynamic collection!


3. Practical Use Cases & Code Examples

A. Staggered Entrance Animations

Instead of writing 15 :nth-child overrides, you can stagger entrance timings with a single line:

/* Before: Manual nth-child boilerplate */
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 80ms; }
.list-item:nth-child(3) { animation-delay: 160ms; }
/* ...repeat forever */

/* After: Single expressive calculation */
.list-item {
  animation: slideIn 0.5s ease-out both;
  animation-delay: calc((sibling-index() - 1) * 80ms);
}

B. Dynamic Color Distribution Across Children

Want every item in a list or tab set to have an evenly spaced hue around the color wheel without hardcoding classes?

.badge {
  /* Distribute hue (0deg - 360deg) based on index and total count */
  background-color: hsl(
    calc(sibling-index() * (360deg / sibling-count())) 
    75% 
    50%
  );
}

If you have 4 badges, they receive hues of $90^\circ, 180^\circ, 270^\circ, 360^\circ$. If you add a 5th badge, CSS automatically redistributes all 5 to $72^\circ, 144^\circ, 216^\circ, 288^\circ, 360^\circ$!

C. Overlapping Stack (Card Deck Effect)

You can calculate z-index and positional offsets mathematically:

.stacked-card {
  position: absolute;
  top: 0;
  left: 0;
  
  /* Layer later cards above earlier ones */
  z-index: sibling-index();
  
  /* Cascade each card downward and rightward */
  transform: translate(
    calc((sibling-index() - 1) * 12px), 
    calc((sibling-index() - 1) * 8px)
  );
}

4. Live Interactive Demo

Try the interactive widget below. Use the buttons to add or remove elements or re-trigger the entrance animation. Notice how the left border hue and entrance delays recalculate automatically based on the items' dynamic positions in the DOM:

Total siblings: 4 • Border hue step: calc(360deg / 4) = 90deg per item.

5. How Does It Compare to CSS Counters?

A common question is: “How is sibling-index() different from counter()?”

Feature counter() sibling-index()
Output Type <string> <integer>
Allowed Properties content in pseudo-elements Any property accepting integer / <calc()>
Setup Required Requires counter-reset & counter-increment None (Automatic)
Mathematical Usage ❌ Cannot be used in calc() ✅ Fully supported in calc(), min(), max()

While counter() is built for displaying numbering in text, sibling-index() provides numerical coordinates directly to the layout and rendering engine.


6. Progressive Enhancement Strategy

To start using sibling-index() today while supporting older clients, combine @supports with CSS custom property fallbacks:

/* Fallback: manual or server-injected custom property */
.card {
  --index: var(--sibling-fallback-index, 1);
  animation-delay: calc(var(--index) * 100ms);
}

/* Modern enhancement */
@supports (animation-delay: calc(sibling-index() * 1s)) {
  .card {
    --index: sibling-index();
  }
}

Conclusion

The sibling-index() and sibling-count() functions represent another massive milestone in making CSS truly dynamic and declarative. By shifting positional awareness into CSS stylesheets, we eliminate brittle JavaScript layout observers, wipe out preprocessor generation loops, and write cleaner, more resilient frontend components.