ScrollStory

Scroll-driven animation,
composed

A shadcn-like primitive for pinned viewport storytelling. Three components. Infinite choreography.
Scroll to explore
Parallax

Depth without effort

One prop. speed controls how fast each layer moves relative to scroll.

<Parallax speed={0.3}>
  <BackgroundImage />
</Parallax>

<Parallax speed={0.7}>
  <Headline />
</Parallax>

<Parallax speed={1.3}>
  <FloatingOrb />
</Parallax>
SceneElement

Choreograph anything

Keyframe arrays distribute evenly across the at range. Two values for A→B. Four values for in‑hold‑out.

opacity + y
{ opacity: [0, 1], y: [40, 0] }
scale pulse
{ scale: [0.8, 1.05, 1] }
rotate
{ rotate: [0, 180, 360] }
blur reveal
{ blur: [8, 0], opacity: [0, 1] }
Counter

Numbers that scroll

0%
Faster prototyping
Less scroll code
0
Component primitives
<Counter
  from={0}
  to={47}
  suffix="%"
  at={[0, 0.5]}
/>
Composition

The escape hatch

Custom components consume useSceneProgress() directly for anything the declarative API can't handle.

function OrbitalViz() {
  const progress = useSceneProgress();

  useMotionValueEvent(progress, "change", (v) => {
    // Drive WebGL, canvas, or any
    // imperative animation from 0→1
    renderer.setProgress(v);
  });

  return <canvas ref={canvasRef} />;
}
Full API

Copy. Paste. Scroll.

ScrollStory
Scene
SceneElement
Parallax
Counter
ProgressIndicator
useStoryProgress()
useSceneProgress()
useSceneId()

Two files. Zero config. Drop into any Next.js project with motion/react.

ScrollStory
Progress: 0.0%

ScrollStory — scroll-story.tsx + smooth-scroll-provider.tsx