# Timeline 01 - Vertical Progress Timeline

> A modern two-column timeline featuring alternating content blocks, visual storytelling elements, and chronological progression—designed to highlight growth and achievements.

A shadcn/ui block from the [@shadcn-space](https://shadcnspace.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcn-space/timeline-01).

- Page, with a live preview: https://shadcn.exchange/r/shadcn-space/timeline-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-space/timeline-01-light-e1cd03b81599a471.png
- Registry item: https://shadcnspace.com/r/timeline-01.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnspace.com/r/timeline-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `motion`
- Registry items: `button`

## Files

### `src/components/shadcn-space/blocks/timeline-01/page.tsx`

```tsx
"use client";

import TimelineBlock01 from "@/components/shadcn-space/blocks/timeline-01";

export default function TimelinePage() {
    return <TimelineBlock01 />;
}
```

### `src/components/shadcn-space/blocks/timeline-01/timeline/index.tsx`

```tsx
"use client";

import { useRef } from "react";
import { cn } from "@/lib/utils";
import { motion, useScroll, useSpring, useTransform } from "motion/react";

export interface TimelineItemProps {
  title: string;
  description: string;
  date: string;
  image?: string;
  className?: string;
}

export interface TimelineProps {
  items: TimelineItemProps[];
  className?: string;
}

const TimelineItem = ({
  title,
  description,
  date,
  image,
  index,
}: TimelineItemProps & { index: number }) => {
  const isEven = index % 2 === 0;

  return (
    <div className="relative grid grid-cols-1 md:grid-cols-2 items-center md:gap-0 gap-6">
      <div
        className={cn(
          "flex flex-col justify-center p-0 md:p-12 z-10",
          isEven ? "md:items-end md:text-right" : "md:order-2"
        )}
      >
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-100px" }}
          transition={{ duration: 0.5, delay: 0.2 }}
          className="flex flex-col gap-3"
        >
          <div className="space-y-2">
            <p className="text-lg md:text-xl text-muted-foreground font-normal">
              ({date})
            </p>
            <h3 className="text-2xl md:text-3xl lg:text-4xl font-medium tracking-tight text-foreground">
              {title}
            </h3>
          </div>
          <p className="text-base md:text-lg text-muted-foreground leading-relaxed">
            {description}
          </p>
        </motion.div>
      </div>

      <div
        className={cn(
          "flex items-center justify-center p-0 md:p-12 z-10",
          isEven ? "md:order-2" : ""
        )}
      >
        {image && (
          <motion.div
            initial={{ opacity: 0, scale: 0.95 }}
            whileInView={{ opacity: 1, scale: 1 }}
            viewport={{ once: true, margin: "-100px" }}
            transition={{ duration: 0.5 }}
            className="relative aspect-4/3 w-full overflow-hidden rounded-2xl"
          >
            <img
              src={image}
              alt={title}
              className="absolute inset-0 h-full w-full object-cover object-top"
            />
          </motion.div>
        )}
      </div>
    </div>
  );
};

export const Timeline = ({ items, className }: TimelineProps) => {
  const containerRef = useRef<HTMLDivElement>(null);

  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start center", "end center"],
  });

  const smoothProgress = useSpring(scrollYProgress, {
    stiffness: 100,
    damping: 30,
    restDelta: 0.001,
  });

  const dotTop = useTransform(smoothProgress, [0, 1], ["0%", "100%"]);

  return (
    <div ref={containerRef} className={cn("relative md:px-0", className)}>
      <div className="absolute md:left-1/2 top-0 bottom-0 w-px md:-translate-x-1/2 bg-border" />
      <div className="absolute md:left-1/2 top-0 bottom-0 w-px md:-translate-x-1/2">
        <motion.div
          style={{ scaleY: smoothProgress, originY: 0 }}
          className="absolute inset-0 bg-foreground w-full"
        />
        <motion.div
          style={{ top: dotTop }}
          className="absolute left-1/2 -translate-x-1/2 -translate-y-1/2 z-30 h-4 w-4 rounded-full border-4 border-background bg-foreground shadow-lg md:block hidden"
        />
      </div>

      <div className="flex flex-col md:px-0 px-6 md:py-0 py-8 md:gap-0 gap-12">
        {items.map((item, index) => (
          <TimelineItem key={index} {...item} index={index} />
        ))}
      </div>
    </div>
  );
};

export default Timeline;
```

### `src/components/shadcn-space/blocks/timeline-01/index.tsx`

```tsx
"use client";

import Timeline from "@/components/shadcn-space/blocks/timeline-01/timeline";
import { Badge } from "@/components/ui/badge";

const timelineData = [
    {
        title: "Agency Founded",
        description:
            "Started with a mission to create meaningful digital experiences through strategy and thoughtful design.",
        date: "2022",
        image:
            "https://images.shadcnspace.com/assets/team/team-5-img-1.webp",
    },
    {
        title: "First Major Clients",
        description:
            "Partnered with ambitious businesses looking to transform their online presence and customer engagement.",
        date: "2024",
        image:
            "https://images.shadcnspace.com/assets/services/services-img-1.webp",
    },
    {
        title: "Global Reach",
        description:
            "Working with clients worldwide while continuing to deliver impactful and measurable digital experiences.",
        date: "2026",
        image:
            "https://images.shadcnspace.com/assets/services/services-08-bg-01.webp",
    },
];

const TimelineBlock01 = () => {
    return (
        <section className="overflow-hidden">
            <div className="max-w-7xl mx-auto px-4 md:px-8 lg:px-16">
                <div className="border-x border-b border-border px-6 py-10 md:px-10 md:py-16 lg:px-16 lg:py-20">
                    <div className="max-w-2xl space-y-4">
                        <Badge
                            variant="outline"
                            className="rounded-full px-3 py-1 font-normal"
                        >
                            Timeline
                        </Badge>
                        <div className="space-y-3">
                            <h2 className="text-3xl md:text-4xl lg:text-5xl font-medium tracking-tight text-foreground">
                                A Story Of Growth
                            </h2>
                            <p className="md:text-lg text-base text-muted-foreground leading-relaxed">
                                Our journey reflects years of collaboration, creativity, and
                                dedication to helping brands stand out online.
                            </p>
                        </div>
                    </div>
                </div>
                <div className="md:border-x border-r border-border">
                    <Timeline items={timelineData} />
                </div>
                <div className="border-x border-t border-border h-18 md:h-28" />
            </div>
        </section>
    );
};

export default TimelineBlock01;
```
