Sticky Stacking Cards

As you scroll, each card sticks a little lower than the one before and the earlier cards shrink back into a neat pile.

  • CSS
  • Layout
  • Scroll
  • Added
Scroll inside the frame ↓
01Brief
02Sketch
03Build
04Launch

AI prompt

Create stacking cards on scroll with CSS in React. Give each card position: sticky with top: calc(16px + var(--i) * 14px), so later cards stop a little lower and overlap the earlier ones like a deck. Use a scroll-driven animation so cards shrink as they get covered: animation: shrink linear both; animation-timeline: view(); animation-range: exit-crossing 0% exit-crossing 60%; with a keyframe to transform: scale(0.9) and filter: brightness(0.7). Add a fallback for browsers without animation-timeline (they simply stack without shrinking). Cards are numbered steps "Brief", "Sketch", "Build", "Launch". Under prefers-reduced-motion, keep the sticky stacking and drop the scale.

Source

Demo.tsx
'use client'

import s from './demo.module.css'

export default function StickyStack({ labels }: { labels: Record<string, string> }) {
  const steps = labels.steps.split('|')
  return (
    <div className={s.frame} tabIndex={0} aria-label={labels.hint}>
      <span className={s.hint}>{labels.hint} ↓</span>
      {steps.map((step, i) => (
        <article key={step} className={s.card} style={{ '--i': i } as React.CSSProperties}>
          <span className={s.n}>{String(i + 1).padStart(2, '0')}</span>
          <strong className={s.step}>{step}</strong>
        </article>
      ))}
      <div className={s.end} />
    </div>
  )
}
demo.module.css
.frame {
  position: absolute;
  inset: 0;
  display: grid;
  justify-items: center;
  gap: 28px;
  padding: 16px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.frame:focus-visible { outline: 2px solid var(--color-focus); outline-offset: -4px; }

.hint { font: 500 var(--text-xs) / 1 var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--stage-muted); }

.card {
  position: sticky;
  top: calc(16px + var(--i) * 14px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(240px, 100%);
  height: 110px;
  padding: 0 20px;
  border: 1px solid var(--stage-rule);
  border-radius: 16px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  transform-origin: 50% 0;
}

.card:nth-of-type(even) { background: var(--stage-ink); color: var(--stage-bg); }

@supports (animation-timeline: view()) {
  .card {
    animation: shrink linear both;
    animation-timeline: view();
    animation-range: exit-crossing 0% exit-crossing 60%;
  }
}

@keyframes shrink { to { transform: scale(0.9); filter: brightness(0.7); } }

.n { font: 500 var(--text-sm) / 1 var(--font-mono); color: var(--stage-accent); }
.step { font: 600 var(--text-xl) / 1 var(--font-display); }

.end { height: 120px; }

@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.