Create timeline in react

Learn how to create timeline component in react.

Timeline component is used to show things or events that have happened in the given frame of time.

React vertical timeline

For developing this component we are going to need few extra packages.

  • prop-types: To validate the props we are going to receive.
  • classnames: It helps us to use CSS classes as javascript objects, we just need to name our CSS file as filename.module.css.

React timeline folder structure

You may have got a good idea about how our file structure is, so let us start the development.

Import all the packages at the top and set the structure of the component.

import React from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./index.module.css";

const TimeLine = props => {
  //Other code will go here
}

export default TimeLine;

We are creating a functional component because it does not need to maintain any state, it will just generate the timeline for the data that has been passed.

There are two different variant of timeline component which we are going to build.

  • Vertical or Horizontal timeline.
  • Generate the timeline in normal or reverse order.

Validate the props to make sure we know what all things are essential for us.

TimeLine.propTypes = {
  events: PropTypes.arrayOf(
    PropTypes.shape({
      time: PropTypes.string.isRequired,
      title: PropTypes.string.isRequired,
      desc: PropTypes.string.isRequired
    })
  ).isRequired,
  orientation: PropTypes.oneOf(["horizontal", "vertical"]).isRequired,
  startFrom: PropTypes.oneOf(["first", "last"]).isRequired
};

TimeLine.defaultProps = {
  orientation: "vertical",
  startFrom: "last"
};

I have kept the data format specific to my requirements, you can update it but remember to make changes while generating it.

Pull all the props.

let { events, orientation, startFrom } = props;

Based on what we have received in the props, If the startFrom is last then reverse the array of input so that the time line is in reverse order.

if (startFrom === "last") {
    events = events.reverse();
}

The orientation will be handled by CSS, we just have to add the proper class so that we can update the design properly.

Generate the graph my mapping over all the data and add appropriate CSS classes.

//Mapped List
  const eventsMappedToElements = events.map((e, i) => (
    <div
      key={e.time}
      className={cx(styles.timelineItem, { [styles.right]: i % 2 !== 0 })}
    >
      <div className={styles.timelineContent}>
        <span className={styles.time}>{e.time}</span>
        <span className={styles.title}>{e.title}</span>
        <p className={styles.desc}>{e.desc}</p>
      </div>
    </div>
  ));

  return (
    <div
      className={cx({
        [styles.vertical]: orientation === "vertical",
        [styles.horizontal]: orientation === "horizontal"
      })}
    >
      {eventsMappedToElements}
    </div>
  );

As per our layout, alternate events will be placed next to each other in opposite direction that is why I have added extra class right to differentiate alternate element.


Complete code of timeline component in react.

import React from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import styles from "./index.module.css";

const TimeLine = props => {
  let { events, orientation, startFrom } = props;

  //Reverse the list
  if (startFrom === "last") {
    events = events.reverse();
  }

  //Mapped List
  const eventsMappedToElements = events.map((e, i) => (
    <div
      key={e.time}
      className={cx(styles.timelineItem, { [styles.right]: i % 2 !== 0 })}
    >
      <div className={styles.timelineContent}>
        <span className={styles.time}>{e.time}</span>
        <span className={styles.title}>{e.title}</span>
        <p className={styles.desc}>{e.desc}</p>
      </div>
    </div>
  ));

  return (
    <div
      className={cx({
        [styles.vertical]: orientation === "vertical",
        [styles.horizontal]: orientation === "horizontal"
      })}
    >
      {eventsMappedToElements}
    </div>
  );
};

TimeLine.propTypes = {
  events: PropTypes.arrayOf(
    PropTypes.shape({
      time: PropTypes.string.isRequired,
      title: PropTypes.string.isRequired,
      desc: PropTypes.string.isRequired
    })
  ).isRequired,
  orientation: PropTypes.oneOf(["horizontal", "vertical"]).isRequired,
  startFrom: PropTypes.oneOf(["first", "last"]).isRequired
};

TimeLine.defaultProps = {
  orientation: "vertical",
  startFrom: "last"
};

export default TimeLine;

Complete style code of timeline component

Style is the major part of our component, because using this we are going to decide the orientation of the timeline.

These are few common thing which are independent of the orientation.

/* Common Style */
.time {
  display: inline-block;
  width: 100%;
  font-size: 24px;
  margin-bottom: 5px;
}

.title {
  display: inline-block;
  width: 100%;
  font-size: 18px;
  margin-bottom: 5px;
}

.desc {
  margin: 0;
}

React vertical timeline

In the vertical orientation we are placing each event next to each other in opposite direction. There is a straight line in between to mark the time.

I have designed this by updating the style in pseudo elements.

/* Vertical */
.vertical {
  position: relative;
  width: 1200px;
  margin: 0 auto;
  padding: 15px;
  background-color: #ce93d8;
}

.vertical:after {
  content: "";
  position: absolute;
  width: 6px;
  background-color: white;
  top: 0;
  bottom: 0;
  left: 50%;
  margin-left: -3px;
}

.vertical .timelineItem {
  width: 50%;
  padding: 10px 45px;
  position: relative;
  text-align: center;
}

.vertical .timelineContent {
  background-color: #ec407a;
  padding: 15px;
  border-radius: 4px;
}

.vertical .timelineItem:before {
  content: " ";
  height: 0;
  position: absolute;
  top: 22px;
  width: 0;
  z-index: 1;
  right: 35px;
  border: medium solid #880e4f;
  border-width: 10px 0 10px 10px;
  border-color: transparent transparent transparent #880e4f;
}

.vertical .timelineItem::after {
  content: "";
  position: absolute;
  width: 25px;
  height: 25px;
  right: -16px;
  background-color: white;
  border: 4px solid #e91e63;
  top: 19px;
  border-radius: 50%;
  z-index: 1;
}

.vertical .timelineItem.right {
  left: 50%;
}

.vertical .timelineItem.right:before {
  left: 35px;
  border-width: 10px 10px 10px 0;
  border-color: transparent #880e4f transparent transparent;
}

.vertical .timelineItem.right::after {
  left: -16px;
}

React horizontal timeline

Creating alternate timeline in opposite direction for horizontal orientation creates lots of style bug as you will need to keep the height of each component consistent.

You can simple keep the events in a single this will remove the headache.

But I have designed for alternate direction only.

/* Horizontal */
.horizontal {
  position: relative;
  height: 926px;
  max-height: 926px;
  overflow-x: auto;
  background: red;
  display: flex;
  flex-wrap: unset;
  align-items: flex-start;
}

.horizontal .timelineItem {
  position: relative;
  flex: 0 0 500px;
  max-width: 500px;
  min-height: 294px;
  padding: 50px 10px;
}

.horizontal .timelineItem:after {
  content: "";
  position: absolute;
  width: 100%;
  height: 5px;
  bottom: 0;
  background: #fff;
}

.horizontal .timelineItem.right {
  align-self: flex-end;
}

.horizontal .timelineItem.right:after {
  display: none;
}

.horizontal .timelineItem.right:before {
  content: "";
  position: absolute;
  width: 100%;
  height: 5px;
  top: 0;
  background: #fff;
}

.horizontal .timelineContent {
  position: relative;
  padding: 30px;
  background-color: #fff;
}

.horizontal .timelineContent:before {
  content: " ";
  height: 0;
  position: absolute;
  bottom: -10px;
  width: 0;
  z-index: 1;
  left: 35px;
  border: medium solid #880e4f;
  border-width: 10px 10px 0 10px;
  border-color: #fff transparent transparent transparent;
}

.horizontal .timelineContent::after {
  content: "";
  position: absolute;
  width: 25px;
  height: 25px;
  left: 27px;
  background-color: white;
  border: 4px solid #e91e63;
  bottom: -65px;
  border-radius: 50%;
  z-index: 1;
}

.horizontal .right .timelineContent::before {
  top: -10px;
  border-width: 0 10px 10px 10px;
  border-color: transparent transparent #fff transparent;
}

.horizontal .right .timelineContent::after {
  top: -65px;
}

Input

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import TimeLine from "./Components/Timeline";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <div className="abc">
    <TimeLine
      events={[
        {
          time: "1942",
          title: "The great plague",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1945",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1946",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1947",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1948",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1949",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        },
        {
          time: "1950",
          title: "The great flu",
          desc:
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"
        }
      ]}
      startFrom={"last"}
      orientation={"horizontal"}
    />
  </div>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Output

React horizontal timeline