parallax /
Sticky-pin Storytelling
Image pins to the viewport with position:sticky while panel content scrolls past it. Pure CSS, perfect on iOS.
Preview
Source
tsx
export interface ParallaxStickyPinProps {
imageSrc?: string;
eyebrow?: string;
panels?: { title: string; body: string }[];
}
export default function ParallaxStickyPin({
imageSrc = "/heroes/sba/bg-2.jpg",
eyebrow = "Parallax · 05",
panels = [
{
title: "Sticky-pin parallax",
body: "The background image pins to the viewport while the foreground panels scroll past it.",
},
{
title: "Pure CSS, zero JS",
body: "Just position: sticky on the image wrapper. Browser handles everything — buttery smooth, even on iOS.",
},
{
title: "Use it for storytelling",
body: "Each panel can reveal a new layer, a new statistic, a new chapter. The image acts as the stage.",
},
],
}: ParallaxStickyPinProps) {
return (
<section className="relative isolate w-full bg-slate-900">
<div className="relative">
{/* Sticky image — pins to the viewport while the content scrolls past it */}
<div className="sticky top-0 -z-10 h-screen w-full overflow-hidden">
<div
className="absolute inset-0"
style={{
backgroundImage: `url(${imageSrc})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/40 via-black/30 to-black/70" />
</div>
{/* Panels — sit on top of the sticky image, scroll normally */}
<div className="relative -mt-screen" style={{ marginTop: "-100vh" }}>
<p className="mx-auto max-w-3xl px-6 pt-32 text-center text-xs font-semibold uppercase tracking-[0.3em] text-white/70">
{eyebrow}
</p>
{panels.map((p, i) => (
<div
key={i}
className="flex min-h-screen items-center justify-center px-6 py-24"
>
<div className="mx-auto max-w-2xl rounded-2xl bg-white/10 p-8 text-center text-white backdrop-blur-md ring-1 ring-white/15 sm:p-12">
<p className="mb-2 text-xs font-semibold uppercase tracking-[0.25em] text-white/60">
Panel {String(i + 1).padStart(2, "0")}
</p>
<h2 className="text-balance text-3xl font-semibold leading-tight sm:text-4xl">
{p.title}
</h2>
<p className="mt-4 text-base leading-relaxed text-white/80 sm:text-lg">
{p.body}
</p>
</div>
</div>
))}
</div>
</div>
</section>
);
} Claude Code Instructions
CLI Install
npx innovations add sticky-pinWhere to use it
position: sticky does all the work. Zero JS. Perfect cross-browser parity (including iOS Safari, unlike classic-fixed).
The image wrapper is sticky with h-screen, the panels container has a negative top margin equal to one viewport so the first panel sits on top of the image.
WHEN TO USE:
- Long-form storytelling (case studies, product narratives, "how we built this" pages)
- Each panel reveals a new piece while the visual stays present
- Great with a video instead of an image (drop a <video autoPlay muted loop playsInline> in place of the bg div)
EXTENDING:
- Pass any number of panels (default 3). Section height auto-grows with each.
- To swap the image partway through, layer multiple sticky-pin sections back-to-back.
- For text-on-image styles, drop the backdrop-blur card and use plain text with a bottom-gradient.