Stagger, but never block
Offset entrances 30–80ms per item; every item is clickable at once.
From find-animation-opportunities, group entrances.
All at once vs. a sequence
Press Replay, then drag the stagger.
All at onceBad
animation-delay: 0msStaggeredGood
calc(var(--i) * 50ms).grid > * {
animation: enter 300ms var(--ease-out) backwards;
animation-delay: min(calc(var(--i) * 50ms), 400ms);
}
@keyframes enter { from { opacity: 0; transform: translateY(8px); } }
Click before it lands
Press Replay, then click card 12 right away.
Waits for the cascadeBad
Live from frame oneGood
.card { animation: enter 300ms var(--ease-out) backwards; }
/* Bad */
.card.entering { pointer-events: none; }
Stagger arrival, not typing
Type in the filter and compare the lists.
10 vendors
Replays per keystrokeBad
cascades: 0Once, on arrivalGood
cascades: 0input.addEventListener("input", () => {
list.classList.remove("entering");
for (const row of rows) row.hidden = !matches(row, input.value);
});
Why it matters, pros and cons
Why it matters
A 30–80ms offset makes a flash a sequence. It is decoration: never gate input.
Pros
- Gives dense grids a reading order.
- One custom property per item.
- Masks uneven data arrival.
Cons
- Pages seen daily: the cascade becomes a wait.
- Long lists: cap the total delay.
- Typing, sorting, paging are interactions, not arrivals.