Scroll timeline
FreeA history down the page: the line fills as you scroll and each entry lights and settles as the line reaches it.
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-timeline.jsonScroll the story.
March 2024
Founded in Dublin
Three people and a spreadsheet that took a week to close the month.October 2024
First bank feed
Transactions arrive every hour instead of from a CSV.May 2025
Multi-entity close
Groups close every entity in one place, with eliminations.January 2026
1,000 teams
Finance teams in 31 countries close with us.October 2026
Any-currency matching
Payments match invoices across currencies at the day's rate.
When to use #
Use it for
- Company history, release history, case studies.
- Order or project histories read top to bottom.
Skip it when
- Event logs people scan; use the timeline or a table.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/scroll-timeline.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { ScrollTimeline } from "@/components/ui/scroll-timeline" const history = [ { id: "h1", date: "March 2024", title: "Founded in Dublin", body: "Three people and a spreadsheet that took a week to close the month." }, { id: "h2", date: "October 2024", title: "First bank feed", body: "Transactions arrive every hour instead of from a CSV." }, { id: "h3", date: "May 2025", title: "Multi-entity close", body: "Groups close every entity in one place, with eliminations." }, { id: "h4", date: "January 2026", title: "1,000 teams", body: "Finance teams in 31 countries close with us." }, { id: "h5", date: "October 2026", title: "Any-currency matching", body: "Payments match invoices across currencies at the day's rate." }, ]; export function ScrollTimelineDemo() { return ( <div className="h-[420px] w-full max-w-md overflow-y-auto rounded-[14px] p-6 shadow-[inset_0_0_0_1px_var(--border)]" tabIndex={0} role="region" aria-label="Company history"> <p className="mb-6 text-sm text-muted-foreground">Scroll the story.</p> <ScrollTimeline items={history} /> <div className="h-48" aria-hidden="true" /> </div> ); }
API reference #
ScrollTimeline
component
Accessibility #
It is built on native elements and keeps aria-hidden in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.