Vertical Timeline

Vertical Timeline

Today's resource is an easy to customize, responsive timeline. We used some CSS3 tricks and a bit of JavaScript to create some bounce animations that affect desktop users only, while on mobile the structure is more minimal.

👉 Important: this experiment is built upon the CodyHouse Framework.

We all are quite familiar with vertical timelines: all instant messaging applications use them. A current trend in web design is to use a similar structure, but to show a process rather than a sequence of events. That is why timeline-like structures are often used for the "How it works" page.

Creating the structure

We wrapped the entire timeline into a <section> element. The .cd-timeline__block div represents a "block" of content. We then split the image/icon and the text content into 2 separates divs.

<section class="cd-timeline js-cd-timeline">
   <div class="container container--lg cd-timeline__container">
      <div class="cd-timeline__block">
         <div class="cd-timeline__img cd-timeline__img--picture">
            <img src="assets/img/cd-icon-picture.svg" alt="Picture">
         </div> <!-- cd-timeline__img -->

         <div class="cd-timeline__content text-component">
            <h2>Title of section 1</h2>
            <p class="text--subtle">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Iusto, optio, dolorum provident rerum aut hic quasi placeat iure tempora laudantium ipsa ad debitis unde? Iste voluptatibus minus veritatis qui ut.</p>

       <div class="flex flex--space-between flex--center-y">
         <span class="cd-timeline__date">Jan 14</span>
         <a href="#0" class="btn btn--subtle">Read more</a>
         </div> <!-- cd-timeline__content -->
      </div> <!-- cd-timeline__block -->

      <div class="cd-timeline__block">
         <!-- ... -->
      </div> <!-- cd-timeline__block -->
</section> <!-- cd-timeline -->

Adding style

We used a ::before selector in absolute position to create the vertical line.

.cd-timeline {
  overflow: hidden;
  padding: var(--space-lg) 0;

.cd-timeline__container {
  position: relative;
  padding: var(--space-md) 0;

  &::before { // this is the timeline vertical line
    content: '';
    position: absolute;
    top: 0;
    left: 18px;
    height: 100%;
    width: 4px;
    background: var(--cd-color-2);

We used Flexbox to align content and image inside each cd-timeline__block element:

.cd-timeline__block {
  display: flex;

  @include breakpoint(md) {
    &:nth-child(even) {
      flex-direction: row-reverse; // for even blocks -> lay out content from right to left

.cd-timeline__img {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-shrink: 0;

  @include breakpoint(md) {
      order: 1; // flex order -> place the image after cd-timeline__content

.cd-timeline__content {
   flex-grow: 1; // expand element so that it takes up all the available space inside its parent

   @include breakpoint(md) {
      width: 45%;
      flex-grow: 0; // prevent element from growing inside its parent

2 bounce-in animations, both for the image/icon and the text content, have been created and are visible only for desktop users. For the animation to work we use 2 classes: .cd-timeline__img--hidden/.cd-timeline__content--hidden, which is used to hide by default all the content blocks that are off the viewport; when the user scrolls down, we add the .cd-timeline__img--bounce-in class to the .cd-timeline__img and the .cd-timeline__content--bounce-in to the .cd-timeline__content to make the elements visible and trigger the animation.

@include breakpoint(md) { // animations
  .cd-timeline__img--hidden, .cd-timeline__content--hidden {
    visibility: hidden;

  .cd-timeline__img--bounce-in {
    animation: cd-bounce-1 0.6s;

@keyframes cd-bounce-1 {
  0% {
    opacity: 0;
    transform: scale(0.5);
  60% {
    opacity: 1;
    transform: scale(1.2);
  100% {
    transform: scale(1);    

Events handling

As explained in the "Adding style" section, we used JavaScript to hide content blocks which are off the viewport, using the class .cd-timeline__img--hidden/.cd-timeline__content--hidden.

When user scrolls and a new block enters the viewport, we add the class .cd-timeline__img--bounce-in and the .cd-timeline__content--bounce-in to the .cd-timeline__img and the .cd-timeline__content (and remove the .cd-timeline__img--hidden and .cd-timeline__content--hidden classes) to trigger the animation.

To do that, we created a VerticalTimeline object and defined a showBlocks() method to reveal the timeline blocks on scrolling:

function VerticalTimeline( element ) {
   this.element = element;
   this.blocks = this.element.getElementsByClassName("cd-timeline__block");
   this.images = this.element.getElementsByClassName("cd-timeline__img");
   this.contents = this.element.getElementsByClassName("cd-timeline__content");
   // ..

VerticalTimeline.prototype.showBlocks = function() {
   var self = this;
   for( var i = 0; i < this.blocks.length; i++) {
         if( self.contents[i].classList.contains("cd-timeline__content--hidden") && self.blocks[i].getBoundingClientRect().top <= window.innerHeight*self.offset ) {
            // add bounce-in animation

💌 Join our newsletter

Get our monthly recap with the latest CodyHouse news