Complete guide to CSS sibling-index() and sibling-count() functions for dynamic staggered animations, dynamic layouts, and zero-JS UI effects that scale automatically.
CSS sibling-index(): Staggered Animations Without JavaScript ๐
- The new
sibling-index()andsibling-count()CSS functions return an elementโs position (1-based) and total sibling count as integers, enabling dynamic calculations without JavaScript or manual:nth-child()selectors โ๏ธ. - These functions work with
calc(),@starting-style, andcontainer queriesto create production-ready staggered animations that automatically recalculate when DOM elements change ๐ก. - No more
forEachloops, CSS custom properties on each item, or animation librariesโpure CSS handles it all with native 120fps performance ๐.
๐ฏ How sibling-index() Works
/* Returns 1, 2, 3, 4, 5 for 5 siblings */
transition-delay: calc((sibling-index() - 1) * 0.08s);
/* Zero-based: 0s, 0.08s, 0.16s, 0.24s, 0.32s */
Key Insight: Subtract 1 from sibling-index() for zero-based timing. First item gets 0s delay, second gets your base delay, etc.
๐๏ธ Core Implementation Pattern
1. Final State (aligned, visible)
.list-item {
opacity: 1;
transform: translateY(0) rotate(0deg);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
/* Magic happens here */
transition-delay: calc((sibling-index() - 1) * 0.08s);
}
2. Initial State (@starting-style)
@starting-style {
.list-item {
/* Dynamic initial rotation per item */
--rotate-deg: calc((sibling-index() - 1) * 2deg);
opacity: 0;
transform: rotate(var(--rotate-deg)) translateY(50px);
}
}
๐ฑ Complete Production Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sibling-index() Staggered Animations</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
margin: 0;
padding: 2rem;
display: grid;
place-items: center;
}
.demo-container {
max-width: 600px;
width: 100%;
}
h1 {
color: white;
text-align: center;
font-size: clamp(1.8rem, 5vw, 3rem);
margin-bottom: 3rem;
line-height: 1.2;
}
/* MAIN STAGGER ANIMATION */
.cards {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: 1.5rem;
}
.card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 2.5rem;
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.1);
/* FINAL STATE */
opacity: 1;
transform: translateY(0) scale(1) rotate(0deg);
/* STAGGER TRANSITION */
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
transition-delay: calc((sibling-index() - 1) * 0.1s);
}
.card-content {
font-size: 1.25rem;
font-weight: 600;
color: #1f2937;
text-align: center;
}
/* INITIAL STATE - Triggers automatic transition */
@starting-style {
.card {
opacity: 0;
transform: translateY(60px) scale(0.9);
/* Dynamic rotation based on position */
rotate: calc((sibling-index() - 1) * 1.5deg * var(--direction, 1));
}
}
/* DYNAMIC BACKGROUND BY POSITION */
.card {
background: hsl(
200deg,
calc(60% + (sibling-index() - 1) * 8%),
calc(95% - (sibling-index() - 1) * 2%)
);
}
/* INTERACTION EFFECTS */
.card:hover {
transform: translateY(-12px) scale(1.05);
transition-delay: 0s !important;
box-shadow:
0 35px 70px -12px rgba(0, 0, 0, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.2);
}
/* CONTROL BUTTONS */
.controls {
display: flex;
gap: 1rem;
justify-content: center;
margin: 2rem 0;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 12px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
/* RESPONSIVE STAGGER SPEED */
@container (max-width: 500px) {
.card {
transition-delay: calc((sibling-index() - 1) * 0.06s);
padding: 2rem;
}
}
</style>
</head>
<body>
<div class="demo-container">
<h1>CSS <code>sibling-index()</code><br><small>Staggered Animations - Zero JS โจ</small></h1>
<ul class="cards" id="cards">
<li class="card">
<div class="card-content">๐จ Creative Design</div>
</li>
<li class="card">
<div class="card-content">โก Lightning Fast</div>
</li>
<li class="card">
<div class="card-content">๐ Modern CSS</div>
</li>
<li class="card">
<div class="card-content">๐ฑ Fully Responsive</div>
</li>
<li class="card">
<div class="card-content">๐ฎ Future-Proof</div>
</li>
<li class="card">
<div class="card-content">โจ Production Ready</div>
</li>
</ul>
<div class="controls">
<button class="btn" onclick="addCard()">โ Add Card</button>
<button class="btn" onclick="removeCard()">โ Remove Card</button>
<button class="btn" onclick="reverseDirection()">๐ Reverse</button>
</div>
</div>
<script>
let reverseDir = false;
function addCard() {
const cards = document.getElementById('cards');
const newCard = document.createElement('li');
newCard.className = 'card';
newCard.innerHTML = `<div class="card-content">โจ New Item #${cards.children.length + 1}</div>`;
cards.appendChild(newCard);
}
function removeCard() {
const cards = document.getElementById('cards');
if (cards.children.length > 1) {
cards.removeChild(cards.lastElementChild);
}
}
function reverseDirection() {
reverseDir = !reverseDir;
document.documentElement.style.setProperty('--direction', reverseDir ? '-1' : '1');
}
</script>
</body>
</html>
๐ง Advanced Patterns & Techniques
1. Center-Out Staggering
.card {
--center-distance: min(
(sibling-index() - 3),
(sibling-count() - sibling-index())
);
transition-delay: calc(var(--center-distance) * 0.08s);
}
2. Dynamic Color Gradients
.card {
background: hsl(
calc(200deg + (sibling-index() - 1) * 20deg),
70%,
calc(90% - (sibling-index() - 1) * 3%)
);
}
3. Proportional Spacing
.card {
margin-left: calc((sibling-index() - 1) / sibling-count() * 100%);
}
๐ก๏ธ Production Fallbacks
/* Feature detection + graceful degradation */
@supports not (transition-delay: calc((sibling-index() - 1) * 0.1s)) {
.card:nth-child(1) { transition-delay: 0s; }
.card:nth-child(2) { transition-delay: 0.1s; }
.card:nth-child(3) { transition-delay: 0.2s; }
.card:nth-child(4) { transition-delay: 0.3s; }
/* Limit to expected max items */
}
/* Universal fallback */
@supports not (sibling-index: 1) {
.card {
transition-delay: 0.1s; /* All animate together */
}
}
โ๏ธ Before vs After Comparison
| Technique | JS + CSS Vars | nth-child | sibling-index() |
|---|---|---|---|
| Code | 50+ lines JS | 10+ CSS rules | 3 lines CSS |
| Bundle | 5KB JS | 0KB | 0KB |
| Dynamic | Manual recalc | Static | Automatic |
| Performance | 60fps | Native | Native 120fps |
| Maintain | Complex | Repetitive | Scalable |
๐ฏ Browser Support (April 2026)
| Browser | Version | Status |
|---|---|---|
| Chrome/Edge | 120+ | โ Full |
| Safari | TP 26+ | โ Full |
| Firefox | Nightly | ๐ก Partial |
| Global | ~82% | ๐ Growing |
๐ฅ Production Checklist
โ
Use calc((sibling-index() - 1) * delay) for zero-based timing
โ
Pair with @starting-style for automatic transitions
โ
Add @supports fallbacks for older browsers
โ
Test dynamic add/remove (staggers recalculate automatically)
โ
Use container queries for responsive timing
โ
Combine with sibling-count() for proportional effects
๐ Real-World Use Cases
- Pricing Cards - Staggered entrance from center-out
- Feature Lists - Left-to-right cascade
- Testimonials - typewriter-style entrance
- Product Grids - Masonry with dynamic spacing
- Navigation Menus - Mobile menu slide-in
sibling-index() is the end of JavaScript stagger loops. Add, remove, reorder itemsโanimations automatically recalculate with zero runtime cost. Pure CSS, production-ready, infinitely scalable โจ.
Continue Reading