All components

Scroll Timeline

Effects

Continuous scroll-linked animation on the native Scroll-Driven Animations API, with a JS fallback that is numerically identical rather than merely similar. Both paths drive one registered custom property, `--st-progress`, so there is a single renderer and only the source of the number changes — the spec's four view-progress ranges and a real cubic-bézier solver make the fallback match to ~1e-6. Set `forceFallback` on one of two instances to check it yourself.

Range
Easing
Effect
native if available
Panel 1

Driven by --st-progress, cover · ease-in-out

Panel 2

Driven by --st-progress, cover · ease-in-out

Panel 3

Driven by --st-progress, cover · ease-in-out

forced fallback
Panel 1

Driven by --st-progress, cover · ease-in-out

Panel 2

Driven by --st-progress, cover · ease-in-out

Panel 3

Driven by --st-progress, cover · ease-in-out

effect="none" · your own CSS

Scroll each panel. The two columns must stay locked together — the left one uses animation-timeline: view() where the browser has it, the right one is forced onto the rAF fallback. Both drive the same custom property, so there is only one thing rendering.

Installation

Terminal
npx shadcn@latest add https://ui.saumyarex.xyz/r/scroll-timeline.json

Props

PropTypeDefaultDescription
range"cover" | "contain" | "entry" | "exit""cover"Which slice of the element's pass through the scrollport maps to 0→1, matching the CSS `animation-range` keywords. `contain` inverts meaning depending on whether the subject fits in the scrollport — shorter, it runs fully-visible-at-bottom → at-top; taller, top-aligned → bottom-aligned.
easing"linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | [number, number, number, number]"linear"Applied identically on both paths: as `animation-timing-function` natively, and through a bundled cubic-bézier solver in the fallback. Custom control points are accepted as a 4-tuple.
effect"fade" | "fade-up" | "zoom" | "reveal" | "parallax" | "progress-bar" | "none""fade-up"Built-in visual, expressed as plain CSS over `var(--st-progress)`. Use `"none"` to get the raw property and write your own — that is the primitive use.
forceFallbackbooleanfalseIgnore the native API and use the rAF path. Exists so the fallback is verifiable: render two instances side by side, set this on one, and they must stay locked at every scroll position.
...propsReact.ComponentProps<"div">Forwarded to the wrapper. `data-driver` reports which path is live — `native`, `fallback`, or `reduced`.

Dependencies

clsxtailwind-merge