Stagger
Staggered entrance animations where each item computes its own delay from sibling-index(). The cascade you'd reach for a motion library to get, in two CSS declarations per item.
sibling-indexexit animations
How it works
sibling-index()computes the delay:motion-safe:[transition-delay:calc(sibling-index()*100ms)]gives each item a delay from its 1-based position among its siblings. No per-item delay classes, no loops, no timers — add or reorder items and the cascade just follows.- The entrance itself is
@starting-style: base classes are the settled state,starting:opacity-0 starting:translate-y-2is the frame each item transitions from the first time it renders.transition-[opacity,translate] duration-500 ease-outdoes the rest. - The demo’s toggle replays the cascade without moving the layout:
peer-checked/replay:*:translate-y-2 peer-checked/replay:*:opacity-0on the list moves every item to its hidden frame, so checking staggers them out and unchecking staggers them back in. Opacity and translate don’t affect layout, so nothing below the list shifts. @starting-stylealso applies when an element comes back fromdisplay: none, so the entrance runs whenever your framework mounts the fragment — not only on full page load.- The delay is gated behind
motion-safe:because the global reduced-motion rule shortens durations but leaves delays alone, and a delayed item holds its invisible starting frame until the delay expires. This is the one motion pattern in the library where the global rule isn’t enough by itself. - The landing page of this site cascades its header and showcase cards with the same
sibling-index()delay trick.
Accessibility
- The motion is decorative. Every item is in the DOM from the first byte, so crawlers, find-in-page, and screen readers see the full list regardless of animation state.
- The demo’s toggle is an
sr-onlycheckbox named by its visible label; the focus ring is forwarded onto the label withpeer-focus-visible/replay:outline-*. - The toggle only fades the items — they keep their space and stay in the accessibility tree. It’s a replay control, not a disclosure; use a real show/hide pattern to actually remove content.
Reduced motion
The global prefers-reduced-motion rule collapses the transition to an instant appearance, and
motion-safe: removes the per-item delays entirely, so no content is ever held invisible.
The fallback story
- Without
sibling-index(), thecalc()is invalid and the delay declaration is dropped — every item animates at once. The entrance still plays, just without the cascade. - Without
@starting-style, there is no starting frame: items render already settled. You lose the animation and nothing else.
Browser support
Minimum stable version per engine, resolved at build time from MDN's browser-compat-data; Baseline status from the official web-features dataset. Everything degrades gracefully — the “when missing” column is the actual behavior, not a broken page.
| Feature | Chrome | Edge | Firefox | Safari | When missing |
|---|---|---|---|---|---|
| sibling-index() function | 138 | 138 | ✕ | 26.2 | The computed delay is invalid, so the declaration is dropped and every item animates at the same time — the entrance still plays, just without the cascade. |
| Entry/exit animations (allow-discrete + @starting-style)† | 117 | 117 | 129 | 17.5 | Elements appear and disappear instantly. Nothing breaks — you just lose the animation. |
† @supports cannot test at-rules, so the badge checks transition-behavior: allow-discrete, which co-shipped with @starting-style in every engine.