A company history with tighter spacing, the current year highlighted and a year rail that follows along.
@keyframeui
registry/demos/arc-scroll-timeline-demo.tsx
"use client";
import * as React from "react";
import { motion, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";
import { ArcScroll } from "@/components/ui/arc-scroll";
const milestones = [
{ year: 2019, label: "Founded", detail: "Two designers, a spare bedroom and a prototype." },
{ year: 2020, label: "Beta", detail: "Version 0.1 ships to forty teams who said yes." },
{ year: 2021, label: "Seed", detail: "A seed round grows the team to twelve." },
{ year: 2022, label: "1.0", detail: "A stable API and our first paying customers." },
{ year: 2023, label: "10k teams", detail: "Ten thousand teams ship with us every week." },
{ year: 2024, label: "Global", detail: "New offices in Berlin and Singapore." },
{ year: 2025, label: "Platform", detail: "Plugins open to third-party developers." },
{ year: 2026, label: "Today", detail: "Still small, still shipping every week." },
];
export function ArcScrollTimelineDemo() {
const scrollRef = React.useRef<HTMLDivElement>(null);
const [active, setActive] = React.useState(0);
const reduceMotion = useReducedMotion();
const milestone = milestones[active];
return (
<div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950">
<div ref={scrollRef} className="relative h-[var(--demo-height,34rem)] overflow-y-auto overscroll-contain">
<ArcScroll
containerRef={scrollRef}
stageClassName="h-[var(--demo-height,34rem)]"
spacing={12}
scrollPerCard={220}
onActiveChange={setActive}
overlay={
<div className="absolute inset-x-0 bottom-8 flex flex-col items-center gap-6 px-6 text-center">
<motion.p
key={milestone.year}
initial={{ opacity: 0, y: reduceMotion ? 0 : 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, ease: "easeOut" }}
className="max-w-xs text-sm text-zinc-600 dark:text-zinc-400"
>
{milestone.detail}
</motion.p>
<ol className="flex gap-1 font-mono text-[11px] tabular-nums">
{milestones.map((item, index) => (
<li
key={item.year}
className={cn(
"relative px-1.5 py-1 transition-colors duration-200",
index === active
? "text-zinc-900 dark:text-zinc-50"
: "text-zinc-400 dark:text-zinc-600",
)}
>
{index === active ? (
<motion.span
layoutId="arc-timeline-tick"
transition={{ type: "spring", stiffness: 500, damping: 40 }}
className="absolute inset-x-1.5 -bottom-0.5 h-px bg-current"
/>
) : null}
{String(item.year).slice(2)}
</li>
))}
</ol>
</div>
}
>
{milestones.map((item, index) => (
<div
key={item.year}
className={cn(
"flex h-40 w-28 flex-col justify-between rounded-2xl p-3 shadow-lg ring-1 transition-colors duration-300 sm:w-32",
index === active
? "bg-zinc-900 text-zinc-50 shadow-zinc-900/20 ring-zinc-900 dark:bg-zinc-50 dark:text-zinc-900 dark:ring-zinc-50"
: "bg-white text-zinc-900 shadow-zinc-900/5 ring-zinc-200 dark:bg-zinc-900 dark:text-zinc-50 dark:shadow-black/40 dark:ring-zinc-800",
)}
>
<span className="font-mono text-[10px] uppercase tracking-widest opacity-60">
{item.label}
</span>
<span className="text-3xl font-semibold tracking-tight tabular-nums">
{item.year}
</span>
</div>
))}
</ArcScroll>
</div>
</div>
);
}
Live preview, examples and props: https://keyframeui.com/components/arc-scroll#arc-scroll-timeline-demo






















