Stacking Scroll Cards — Free HTML CSS JS Sticky Snippet
Stacking Scroll Cards · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Stacking Scroll Cards — Sticky Cards That Layer on Scroll

Stacking scroll cards are the storytelling pattern where full-width cards pin to the top of the viewport one after another, and as you keep scrolling the next card slides up to cover the previous one — which shrinks back like a card going into a deck. It's a favorite for explaining a product in numbered steps. This snippet builds the effect in plain HTML, CSS, and a small amount of vanilla JavaScript, leaning on position: sticky so the heavy lifting is native.
Sticky positioning does the pinning
Each card is position: sticky; top: 90px. As you scroll, a card scrolls normally until its top hits 90px from the viewport top, then it sticks there while the rest of the page continues moving. Because all cards share the same sticky offset, the next card scrolls up from below and slides directly over the pinned one — no JavaScript is needed for the pinning itself, which keeps it smooth even on low-end devices. The cards have descending nothing; they simply stack in document order with their drop shadows separating the layers.
JavaScript adds the depth cues
Pure sticky stacking looks flat — the covered card just disappears behind the next. The JavaScript adds the physical "into the deck" feel by measuring, each frame, how close the next card has risen to the pin line. It computes a progress value p from the gap between the next card's top and the pin position, normalized over roughly 70% of the viewport height. As p goes from 0 to 1, the pinned card scales down up to 8% via transform: scale and dims up to 25% via filter: brightness. The combination makes each card recede as it's buried, so the stack reads as real layers.
transform-origin keeps the shrink anchored
The scale uses transform-origin: top center so cards shrink toward their top edge rather than their middle. That keeps the top of each card aligned at the pin line as it shrinks, so the visible "spine" of the stack stays put — exactly how a physical deck looks from the front.
Efficient scroll handling
The scroll listener is { passive: true } and throttled through requestAnimationFrame with a ticking guard, so at most one measurement-and-write happens per frame no matter how many scroll events fire. Reads (getBoundingClientRect) and writes (setting transform and filter) are batched in the single rAF callback. A resize listener re-runs the update because viewport height feeds the progress math.
Numbered, colorful steps
Each card carries a CSS custom property for its background color and a number, making it a natural fit for "how it works" sections. The content is centered vertically and capped in width for readability. Because color is a variable, theming the stack is a per-card one-liner.
Customizing it
Adjust top: 90px to change where cards pin, tune the 0.08 scale and 0.25 brightness factors for a stronger or subtler recede, and change the 0.7 span to make the effect happen over more or less scroll. Add or remove cards freely — the script reads them from the DOM and links each to its successor. Pair it with a feature tabs showcase or follow it with an animated gradient CTA to convert at the end of the story.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't need to work out how the progress value ties sticky positioning to the scale-and-dim effect by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the update function derives progress p from the next card's distance to the pin line, or why transform-origin is set to top center rather than center so the shrinking card's top edge stays anchored at the pin line. The same assistant can help optimize it, for example checking whether the rAF-throttled scroll handler with its ticking flag is correctly avoiding layout thrashing when getBoundingClientRect reads and style writes are interleaved across many cards. It's also useful for extending the feature: ask it to add a progress-driven color or opacity shift on the card's inner content (not just the whole card), support horizontal stacking for a side-scrolling variant, or add a subtle rotation to the depth cue alongside the existing scale and brightness. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a sticky, stacking scroll-card effect in plain HTML, CSS, and JavaScript, no framework, no libraries.
Requirements:
- Multiple full-width card sections, each with position sticky and the exact same top offset value, so each pins near the top of the viewport in turn as the page scrolls and the next card visually slides up to cover it.
- In JavaScript, for every card that has a following card, measure on each scroll event how close the next card's top edge has risen toward the pin line (using getBoundingClientRect, not scroll position alone), and convert that distance into a normalized progress value between 0 and 1 based on a configurable fraction of the viewport height.
- Use that progress value to scale the currently-pinned card down by up to a small percentage (for example up to 8 percent) via a CSS transform, and to dim it via a brightness filter, so the covered card visually recedes rather than abruptly disappearing behind the next one.
- Set transform-origin to the top center of each card so the shrink is anchored at the top edge (where the sticky pin line is), keeping the visible "spine" of the stack aligned rather than shrinking toward the card's center.
- Throttle the scroll handler with requestAnimationFrame and a boolean guard flag so at most one measurement-and-style-update pass runs per animation frame regardless of how many scroll events fire, and register the scroll listener as passive.
- Recompute the effect on window resize as well as scroll, since the progress math depends on viewport height.
- Read the set of cards from the DOM automatically (not a hardcoded count), so adding or removing card sections in the HTML requires no JavaScript changes.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSFour colorful numbered cards stack vertically with a scroll prompt.
- 2Scroll downEach card pins near the top of the viewport in turn.
- 3Keep scrollingThe next card slides over the pinned one, which shrinks back.
- 4Watch the dimmingCovered cards also darken slightly to recede into the deck.
- 5Add or remove cardsThe script reads cards from the DOM automatically.
- 6Tune the depthAdjust the pin offset, scale, and brightness factors.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Each card is position: sticky with the same top: 90px. A card scrolls normally until its top reaches the pin line, then sticks there while the page keeps moving, so the next card rises from below and slides over it. The pinning is entirely native CSS, which keeps it smooth even without JavaScript.
Sticky stacking alone looks flat. The script measures how close the next card has risen to the pin line each frame, derives a progress value, and uses it to scale the pinned card down up to 8% and dim it up to 25%. Those depth cues make each card recede like it's going into a physical deck rather than just vanishing.
So cards shrink toward their top edge instead of their center. That keeps the top of every card aligned at the pin line as it scales down, preserving the visible stacked spine. Shrinking from the center would pull the tops downward and break the layered look.
Yes. The scroll listener is passive and throttled with requestAnimationFrame guarded by a ticking flag, so there's at most one update per frame regardless of how many scroll events fire. All layout reads and style writes happen together in that single rAF callback to avoid layout thrashing, and a resize listener refreshes the viewport-height math.
Render the cards from an array with their color and step text. The sticky CSS works as-is. Put the scroll and resize listeners in a mount effect with cleanup, and query the card elements via a container ref rather than getElementById. Keep the ticking flag in a ref. In Tailwind, use sticky top-[90px], and apply the scale and brightness through inline styles updated in the effect.