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>
);
}
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:
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>
);
}
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>
);
}
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>
);
}
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
| Prop | Goes on | What it does |
|---|---|---|
animate | VerticalTimeline | Fade the cards in on scroll (default true) |
layout | VerticalTimeline | 2-columns, 1-column-left or 1-column-right |
lineColor | VerticalTimeline | Colour of the centre line |
date | VerticalTimelineElement | The text opposite the card |
icon / iconStyle | VerticalTimelineElement | The circle on the line and its colours |
contentStyle / contentArrowStyle | VerticalTimelineElement | Card and arrow styling |
visible | VerticalTimelineElement | Force a card visible without scrolling |
iconOnClick / onTimelineElementClick | VerticalTimelineElement | Click handlers |
More React component guides worth reading:
- Build a React date picker component
- Build a React checkbox component
- Build a React carousel component
- Build a React JSON viewer component
- React component naming conventions
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.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn