How to Use react-vertical-timeline-component in React

react-vertical-timeline-component renders a vertical timeline in React: a centre line with cards alternating left and right, each with a date and an icon. You install it, import its stylesheet, and put a <VerticalTimelineElement> inside a <VerticalTimeline> for every event. This guide builds one step by step, with a screenshot of what each version actually renders, and covers styling, rendering from data, filtering, the one-column layout, and the missing-stylesheet problem that makes most first attempts look broken.

Every example here was built and rendered with React 19.3.0 and react-vertical-timeline-component 4.0.0, and each screenshot is the real component in a browser. Reference: the package on npm.

Install react-vertical-timeline-component

Two imports are needed: the components, and the stylesheet that positions them. Version 4 lists React 16.8 through 19 as peer dependencies, so it installs cleanly on a current React project without --legacy-peer-deps:

npm install react-vertical-timeline-component
import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

A basic React vertical timeline

Each element takes a date, an icon and whatever you want inside the card. The two class names below are the library’s own, and the stylesheet gives them the title and subtitle styling:

import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

export default function App() {
  return (
    <VerticalTimeline animate={false} lineColor="#d7dee8">
      <VerticalTimelineElement
        date="March 2026"
        iconStyle={{ background: "#0b6bcb", color: "#fff" }}
        icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>1</span>}
      >
        <h3 className="vertical-timeline-element-title">Design review</h3>
        <h4 className="vertical-timeline-element-subtitle">Product</h4>
        <p>Signed off the new checkout flow with the design team.</p>
      </VerticalTimelineElement>

      <VerticalTimelineElement
        date="April 2026"
        iconStyle={{ background: "#0b6bcb", color: "#fff" }}
        icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>2</span>}
      >
        <h3 className="vertical-timeline-element-title">Beta release</h3>
        <h4 className="vertical-timeline-element-subtitle">Engineering</h4>
        <p>Shipped the beta to 400 customers in Austin and Denver.</p>
      </VerticalTimelineElement>
    </VerticalTimeline>
  );
}
A React vertical timeline with two cards, March and April 2026, on either side of a centre line with blue numbered icons
The real output: cards alternate sides, with the date opposite each one.

animate={false} is used here so the cards appear immediately. By default they fade in as you scroll, which is an IntersectionObserver effect; turn it off for print pages, tests and screenshots.

Why your timeline looks broken

This is the most common problem with the library, and it is always the same cause: the stylesheet was never imported. Without it you get plain text and bullet-less list items, no line, no cards:

The same React timeline rendered without the stylesheet, showing plain unstyled text instead of cards and a centre line
The same component, without style.min.css. Nothing is wrong with your JSX.

Import react-vertical-timeline-component/style.min.css once, anywhere in the app. If you use CSS modules or a bundler that blocks CSS imports from node_modules, copy the file into your own styles instead.

Style the cards, arrows and icons

Three props do the visual work: contentStyle for the card, contentArrowStyle for the little pointer, and iconStyle for the circle on the line. Grouping them into objects per status keeps the JSX readable:

import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

const done = {
  contentStyle: { background: "#eef5ff", color: "#10243e", borderTop: "4px solid #0b6bcb" },
  contentArrowStyle: { borderRight: "7px solid #eef5ff" },
  iconStyle: { background: "#0b6bcb", color: "#fff" },
};

const planned = {
  contentStyle: { background: "#fff8e6", color: "#4a3a05", borderTop: "4px solid #f0a202" },
  contentArrowStyle: { borderRight: "7px solid #fff8e6" },
  iconStyle: { background: "#f0a202", color: "#fff" },
};

const Dot = ({ label }) => (
  <span style={{ display: "grid", placeItems: "center", height: "100%", fontWeight: 700 }}>{label}</span>
);

export default function App() {
  return (
    <VerticalTimeline animate={false} lineColor="#d7dee8">
      <VerticalTimelineElement date="April 2026" {...done} icon={<Dot label="OK" />} shadowSize="small">
        <h3 className="vertical-timeline-element-title">Beta release</h3>
        <h4 className="vertical-timeline-element-subtitle">Engineering</h4>
        <p>Shipped the beta to 400 customers.</p>
      </VerticalTimelineElement>

      <VerticalTimelineElement date="May 2026" {...planned} icon={<Dot label="..." />} shadowSize="small">
        <h3 className="vertical-timeline-element-title">Public launch</h3>
        <h4 className="vertical-timeline-element-subtitle">Marketing</h4>
        <p>Launch to all accounts, with a webinar the same week.</p>
      </VerticalTimelineElement>
    </VerticalTimeline>
  );
}
A React vertical timeline with a blue completed card and an orange planned card, each with matching arrow and icon colours
Blue for finished work, amber for planned: the card, its arrow and the icon share a colour.

shadowSize takes "small", "medium" or "large", and lineColor on the parent changes the centre line.

Render timeline events from data

In a real application the events come from an API or a JSON file, so you map over them. Give each element a key from your data, not the array index, for the same reason as any other list you loop over:

import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

const events = [
  { id: 1, date: "March 2026", title: "Design review", team: "Product", status: "done",
    text: "Signed off the new checkout flow with the design team." },
  { id: 2, date: "April 2026", title: "Beta release", team: "Engineering", status: "done",
    text: "Shipped the beta to 400 customers in Austin and Denver." },
  { id: 3, date: "May 2026", title: "Public launch", team: "Marketing", status: "planned",
    text: "Launch to all accounts, with a webinar the same week." },
];

const colors = { done: "#0b6bcb", planned: "#f0a202" };

export default function App() {
  return (
    <VerticalTimeline animate={false} lineColor="#d7dee8">
      {events.map((event) => (
        <VerticalTimelineElement
          key={event.id}                       // a stable id, not the array index
          date={event.date}
          iconStyle={{ background: colors[event.status], color: "#fff" }}
          icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>{event.id}</span>}
        >
          <h3 className="vertical-timeline-element-title">{event.title}</h3>
          <h4 className="vertical-timeline-element-subtitle">{event.team}</h4>
          <p>{event.text}</p>
        </VerticalTimelineElement>
      ))}
    </VerticalTimeline>
  );
}

Keeping a status field on each event lets one lookup object drive the icon colour, which is much easier to maintain than a copy of the styles in every element.

Filter the timeline

Because the timeline is just a list of children, filtering is ordinary React state: keep the chosen status, filter the array, and render what is left. The screenshot below was taken after a real click on the done button, so it shows the filtered result, not a mock-up. Related: building a checkbox component for the controls themselves:

import { useState } from "react";
import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

const events = [
  { id: 1, date: "March 2026", title: "Design review", team: "Product", status: "done",
    text: "Signed off the new checkout flow with the design team." },
  { id: 2, date: "April 2026", title: "Beta release", team: "Engineering", status: "done",
    text: "Shipped the beta to 400 customers in Austin and Denver." },
  { id: 3, date: "May 2026", title: "Public launch", team: "Marketing", status: "planned",
    text: "Launch to all accounts, with a webinar the same week." },
];

const colors = { done: "#0b6bcb", planned: "#f0a202" };

export default function App() {
  const [status, setStatus] = useState("all");
  const shown = status === "all" ? events : events.filter((e) => e.status === status);

  return (
    <div>
      <div style={{ fontFamily: "system-ui", padding: "8px 0" }}>
        {["all", "done", "planned"].map((value) => (
          <button key={value} id={`btn-${value}`} onClick={() => setStatus(value)}
            style={{ marginRight: 8, padding: "6px 12px", borderRadius: 6, cursor: "pointer",
                     border: "1px solid #0b6bcb", background: status === value ? "#0b6bcb" : "#fff",
                     color: status === value ? "#fff" : "#0b6bcb" }}>
            {value}
          </button>
        ))}
        <span style={{ marginLeft: 8 }}>showing {shown.length} of {events.length}</span>
      </div>

      <VerticalTimeline animate={false} lineColor="#d7dee8">
        {shown.map((event) => (
          <VerticalTimelineElement
            key={event.id}
            date={event.date}
            iconStyle={{ background: colors[event.status], color: "#fff" }}
            icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>{event.id}</span>}
          >
            <h3 className="vertical-timeline-element-title">{event.title}</h3>
            <h4 className="vertical-timeline-element-subtitle">{event.team}</h4>
            <p>{event.text}</p>
          </VerticalTimelineElement>
        ))}
      </VerticalTimeline>
    </div>
  );
}
A React vertical timeline filtered to show only completed events after clicking the done button, with a count showing two of three
After clicking done: two of three events, and the count updates with them.

One-column layout for narrow screens

The default 2-columns layout only alternates while the timeline is wider than 1170px; below that the library switches to a single column by itself, with the date moving inside the card. You can force that layout at any width with layout="1-column-left", which puts the line on the left and every card to its right, and it is usually the better choice inside a dashboard panel or a sidebar:

import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

const events = [
  { id: 1, date: "March 2026", title: "Design review", team: "Product", status: "done",
    text: "Signed off the new checkout flow with the design team." },
  { id: 2, date: "April 2026", title: "Beta release", team: "Engineering", status: "done",
    text: "Shipped the beta to 400 customers in Austin and Denver." },
  { id: 3, date: "May 2026", title: "Public launch", team: "Marketing", status: "planned",
    text: "Launch to all accounts, with a webinar the same week." },
];

export default function App() {
  return (
    <VerticalTimeline layout="1-column-left" animate={false} lineColor="#0b6bcb">
      {events.map((event) => (
        <VerticalTimelineElement
          key={event.id}
          date={event.date}
          dateClassName="timeline-date"
          iconStyle={{ background: "#0b6bcb", color: "#fff" }}
          icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>{event.id}</span>}
        >
          <h3 className="vertical-timeline-element-title">{event.title}</h3>
          <p>{event.text}</p>
        </VerticalTimelineElement>
      ))}
    </VerticalTimeline>
  );
}
A React vertical timeline in the one column left layout with all three cards stacked to the right of a blue line
1-column-left: the line stays on the left and the cards stack beside it.

Click handlers

Both the card and the icon can respond to clicks, which is how you open a detail panel or navigate to the related record:

import { VerticalTimeline, VerticalTimelineElement } from "react-vertical-timeline-component";
import "react-vertical-timeline-component/style.min.css";

export default function App() {
  return (
    <VerticalTimeline animate={false}>
      <VerticalTimelineElement
        id="launch"
        date="May 2026"
        iconStyle={{ background: "#0b6bcb", color: "#fff" }}
        icon={<span style={{ display: "grid", placeItems: "center", height: "100%" }}>3</span>}
        iconOnClick={() => console.log("icon clicked")}
        onTimelineElementClick={() => console.log("card clicked")}
      >
        <h3 className="vertical-timeline-element-title">Public launch</h3>
        <p>Click the card or the icon.</p>
      </VerticalTimelineElement>
    </VerticalTimeline>
  );
}

Console output from clicking the card and then the icon:

card clicked
icon clicked
PropGoes onWhat it does
animateVerticalTimelineFade the cards in on scroll (default true)
layoutVerticalTimeline2-columns, 1-column-left or 1-column-right
lineColorVerticalTimelineColour of the centre line
dateVerticalTimelineElementThe text opposite the card
icon / iconStyleVerticalTimelineElementThe circle on the line and its colours
contentStyle / contentArrowStyleVerticalTimelineElementCard and arrow styling
visibleVerticalTimelineElementForce a card visible without scrolling
iconOnClick / onTimelineElementClickVerticalTimelineElementClick handlers

More React component guides worth reading:

Frequently asked questions

How do I install react-vertical-timeline-component?

Run npm install react-vertical-timeline-component, then import the two components and react-vertical-timeline-component/style.min.css.

Does react-vertical-timeline-component work with React 18 and 19?

Yes. Version 4 lists React 16.8 to 19 as peer dependencies, so it installs on React 19 without --legacy-peer-deps.

Why is my vertical timeline not styled?

The stylesheet is missing. Import react-vertical-timeline-component/style.min.css once in your app and the line and cards appear.

How do I stop the cards fading in?

Pass animate={false} to <VerticalTimeline>, or visible on a single element to force it to show.

How do I show API data in the timeline?

Map over your array and return one <VerticalTimelineElement> per record, with a key taken from a stable id.

How do I change the colour of a card and its icon?

Use contentStyle for the card, contentArrowStyle for the pointer and iconStyle for the circle, keeping the colours in sync.

Can I put the timeline on one side instead of alternating?

Yes, set layout="1-column-left" (or 1-column-right) on the parent component.

Leave a Comment