Go to homepage

Projects /

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.

Vertical Timeline
Check our new component library →

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.

👋 A new version of this component is available. Download now →.

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 max-width-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="color-contrast-medium">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 justify-between items-center">
          <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

Level up your CSS skills

Each month we email a 1-minute CSS tutorial to 20K developers

Awesome! We just sent you a confirmation link by email

Error - please try again or contact us

Your email address is already subscribed

Project duplicated

Project created

Globals imported

There was an error while trying to export your project. Please try again or contact us.