component
Scroll-pinned hero where a front card flips to reveal a stack of back cards, dismissed one at a time as you keep scrolling.
Loading code…import { StickyScrollCards } from "@/components/sora-ui/catalog/sticky-scroll-cards";
const front = {
title: "First Frame",
description: "A single moment, held in place before everything begins to move.",
};
const cards = [
{ title: "Final Hold", description: "Everything settles into place." },
{ title: "Layered Time", description: "Moments stack and reveal themselves." },
{ title: "Weight & Flow", description: "Elements ease in and out with balance." },
{ title: "Soft Motion", description: "Subtle shifts build a quiet rhythm." },
];
export default function Section() {
return (
<StickyScrollCards
cards={cards}
front={front}
headline="Scroll to pin, flip, and let go"
/>
);
}variant="studio")Cream canvas, condensed display type, and the original Codegrid "MaximaTherapy" palette cycled across back cards:
<StickyScrollCards
cards={cards}
front={front}
headline="Scroll to pin, flip, and let go"
variant="studio"
/>variant="minimal" (default) uses theme tokens (bg-background/bg-foreground).
Override any slot via the classNames prop.
Import STICKY_SCROLL_CARDS_STUDIO_CLASSES to extend the studio preset:
import {
STICKY_SCROLL_CARDS_STUDIO_CLASSES as studio,
StickyScrollCards,
} from "@/components/sora-ui/catalog/sticky-scroll-cards";
<StickyScrollCards
cards={cards}
classNames={{ front: cn(studio.front, "shadow-2xl") }}
front={front}
headline={headline}
variant="studio"
/>;embedded = CSS sticky scroll track (no GSAP pin) sized for a preview panel. Pass
scroller when inside a scroll panel.
<StickyScrollCards
cards={cards}
containerQuery
embedded
front={front}
headline={headline}
scroller={viewport}
variant="studio"
/>Total scroll distance is dismissStart + cards.length × cardDismissDuration (svh).
Back cards dismiss one at a time in reverse order — the last card in the array
leaves first.
<StickyScrollCards
cards={cards}
front={front}
headline={headline}
timing={{
cardsEnterEnd: 500,
cardFlipTrigger: 1000,
dismissStart: 1500,
cardDismissDuration: 500,
flipTiltAngles: [-10, -20, -5, 10],
dismissTiltAngles: [-50, -60, -45, 50],
}}
/>flipTiltAngles/dismissTiltAngles are cycled by index — pass fewer values than
cards.length and they repeat.
3D flip
The front card flips rotationY: 0 → 180 while back cards flip -180 → 0, so
both live in the same stacking position with backface-visibility: hidden. If
you override classNames.front/classNames.back, keep backface-visibility
intact or the flip will show the reverse face mid-turn.
Card colors
In variant="studio", back card colors come from a cycled palette applied as
inline style, not Tailwind classes — classNames.back layers on top via
className, it doesn't replace the color.
className styles the root section. Pass any of these to classNames to
override an inner layer:
headline — hero headline wrappercardsLayer — 3D perspective containerfront / back — card surfacesfrontIcon / backIcon — icon circlebadge — front-only pill (e.g. "Start here")title / description — card texttrack — scroll track (embedded mode only)| Prop | Type | Default |
|---|---|---|
front? | StickyScrollCardsItem | - |
cards? | StickyScrollCardsItem[] | - |
headline? | string | - |
badgeLabel? | string | "Start here" |
variant? | "minimal" | "studio" | minimal |
embedded? | boolean | false |
scroller? | Element | Window | - |
refreshPriority? | number | -1 |
timing? | StickyScrollCardsTiming | - |
className? | string | - |
classNames? | StickyScrollCardsClassNames | - |
| Prop | Type | Default |
|---|---|---|
title? | string | - |
description? | string | - |
icon? | ReactNode | - |
Use StickyScrollCards for a hero moment that needs to hold attention through a
reveal-then-dismiss sequence — feature highlights, a portfolio's opening act, or
any section where a single held frame should fan out into a short, deliberate
series before releasing the scroll. Fewer than 3 back cards makes the dismiss
phase feel abrupt; 3–5 gives the rhythm room to land.
All card headings, icons, badges, and descriptions stay in the document flow and remain accessible to screen readers throughout the sequence.
Inspired by Codegrid. Reimplemented for GSAP and vanilla JS.
Built by Axyl. A motion-first component registry for React.