Scroll Timeline
A scroll-driven timeline where the growing line extends along the main axis and each node lights up and reveals its content as the line arrives.
npx shadcn@latest add https://webberui.com/r/scroll-timeline.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ScrollTimeline />
Installation
npx shadcn@latest add https://webberui.com/r/scroll-timeline.jsonOr, once registries are configured in components.json, install it as @webberui/scroll-timeline.
Usage
Wrap a set of ScrollTimelineItem elements in ScrollTimeline. As you scroll, the growing line on the central axis extends from top to bottom and each node lights up as the line reaches it.
import { Flag, Rocket } from "lucide-react";
import {
ScrollTimeline,
ScrollTimelineItem,
} from "@/components/ui/scroll-timeline";
<ScrollTimeline>
<ScrollTimelineItem icon={<Flag />} date="2024 · Q1" title="Project kickoff">
Settled on an animation-first design principle.
</ScrollTimelineItem>
<ScrollTimelineItem icon={<Rocket />} date="2025 · Q1" title="Public launch">
Scroll to here and the node lights up together with the growing line.
</ScrollTimelineItem>
</ScrollTimeline>;If the timeline sits inside a fixed-height nested scroll container, pass that container's ref to container:
const scrollerRef = React.useRef<HTMLDivElement>(null);
<div ref={scrollerRef} className="h-[300px] overflow-y-auto">
<ScrollTimeline container={scrollerRef}>{/* ... */}</ScrollTimeline>
</div>;Props
ScrollTimeline
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | A set of ScrollTimelineItem elements |
container | React.RefObject<HTMLElement | null> | — | Ref of the nested overflow scroll container; when omitted, the window is the scroll container |
activationLine | number | 0.5 | Position of the trigger line within the scroll viewport (0 = top, 1 = bottom); both the tip of the growing line and node activation use it as their reference |
className | string | — | Appended to the class of the outermost container |
ScrollTimelineItem
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | — | Node title |
date | React.ReactNode | — | Date or stage label, shown above the title |
icon | React.ReactNode | — | Node icon (a lucide-react icon is recommended), shown inside the node once it lights up |
children | React.ReactNode | — | Body text describing the node |
className | string | — | Appended to the class of the node's li |
How it works
- The tip of the growing line and the moment each node lights up both use the same "trigger line" (
activationLine) as their reference, so no matter how much the nodes differ in height, a node always lights up the instant the growing line reaches it — the two stay precisely in sync. - The end of the main axis carries a "comet tail" glow that follows the leading edge of the growing line, reinforcing the sense of scroll direction.
- Nodes and their content brighten smoothly from faded as the scroll progresses; nodes not yet reached stay as hollow shapes with a pale outline.
Accessibility
- When the user has "reduce motion" enabled at the system level, the timeline is presented statically with the whole line lit and all content visible, and it does not change with the scroll.
- The axis, growing line, nodes, and glow are decorative only, so they are marked
aria-hidden; the timeline content is carried by a semanticol/listructure, which assistive technology reads out as a complete list. - The nested scroll container example adds
tabIndexand anaria-labelso keyboard users can focus it and scroll with the arrow keys.
Velocity Skew
Skews and blurs content in real time based on scroll speed, springing back level when the scroll stops, for a reading feel full of kinetic energy.
Section Snap Pin
Section pinning plus snap-scroll choreography — full-height sections snap one by one to fill the viewport, content reveals and exits in order, with side navigation dots.