01List cascade
List items rise from slightly below and slightly scaled down, popping into place one after another for a more dimensional stagger than a plain fade.
Best forNotification feeds, search result lists
scale+translateY90ms 간격ease-pop

<main class="stage">
<ul class="cascade is-demo">
<li>New message</li>
<li>Payment complete</li>
<li>Points earned</li>
</ul>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-base; // 항목 하나가 나타나는 시간
$easing: $ease-pop; // 톡 튀어 올라오는 느낌
$stagger: 90ms; // 항목 사이 시차
@include stage($stage-ink);
.cascade {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
max-width: min(400px, 90vw);
font-weight: 600; font-size: clamp(12px, 3.4vw, 15px); line-height: 1.2; font-family: inherit;
li {
padding: clamp(8px, 2.6vw, 11px) clamp(12px, 3.4vw, 16px);
border-radius: $r-pill;
background: $subject-cream;
color: $stage-ink;
box-shadow: $shadow-raised;
animation: cascade-loop $dur-loop $easing infinite;
white-space: nowrap;
}
@for $i from 1 through 3 {
li:nth-child(#{$i}) { animation-delay: ($i - 1) * $stagger; }
}
}
@keyframes cascade-loop {
0%, 8% { opacity: 0; transform: translateY(14px) scale(.9); }
26%, 66% { opacity: 1; transform: translateY(0) scale(1); }
86%, 100% { opacity: 0; transform: translateY(-8px) scale(.96); }
}
@media (prefers-reduced-motion: reduce) {
.cascade li { animation: none; opacity: 1; transform: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cascade {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
max-width: min(400px, 90vw);
font-weight: 600;
font-size: clamp(12px, 3.4vw, 15px);
line-height: 1.2;
font-family: inherit;
}
.cascade li {
padding: clamp(8px, 2.6vw, 11px) clamp(12px, 3.4vw, 16px);
border-radius: 999px;
background: #fff7e6;
color: #17141a;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
animation: cascade-loop 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
white-space: nowrap;
}
.cascade li:nth-child(1) {
animation-delay: 0ms;
}
.cascade li:nth-child(2) {
animation-delay: 90ms;
}
.cascade li:nth-child(3) {
animation-delay: 180ms;
}
@keyframes cascade-loop {
0%, 8% {
opacity: 0;
transform: translateY(14px) scale(0.9);
}
26%, 66% {
opacity: 1;
transform: translateY(0) scale(1);
}
86%, 100% {
opacity: 0;
transform: translateY(-8px) scale(0.96);
}
}
@media (prefers-reduced-motion: reduce) {
.cascade li {
animation: none;
opacity: 1;
transform: none;
}
}import type { CSSProperties } from "react";
import styles from "./ListCascade.module.scss";
export type ListCascadeProps = {
items: string[];
/** 항목 하나가 나타나는 시간 (기본 300ms) */
duration?: number;
easing?: string;
/** 항목 사이 시차 ms (기본 90ms) */
stagger?: number;
};
export default function ListCascade({
items,
duration = 300,
easing = "cubic-bezier(.2,1.4,.4,1)",
stagger = 90,
}: ListCascadeProps) {
return (
<ul className={styles.cascade} style={{ "--duration": `${duration}ms`, "--easing": easing } as CSSProperties}>
{items.map((label, i) => (
<li key={label} style={{ animationDelay: `${i * stagger}ms` }}>
{label}
</li>
))}
</ul>
);
}01 List cascade — Measured area 9.301% · moving frames 12/23Original by GODRICH · SCSS and React ship in the zip · 9 CSS Stagger Animation Demos You Can Copy-Paste
The post this demo lives in
9 CSS Stagger Animation Demos You Can Copy-PasteA css stagger animation gives each item in a list or grid its own delay, so a group that starts together finishes appear See all demos in this post


