7 days of unlimited WordPress themes, plugins & graphics - for free!* Unlimited asset downloads! Start 7-Day Free Trial
  1. Web Design
  2. Patterns

Building a Vertical Timeline With CSS and a Touch of JavaScript

Scroll to top
Read Time: 9 mins

In this tutorial, we’ll learn how to build a responsive vertical timeline from scratch.

First, we’ll create the basic structure with minimal markup and the power of CSS pseudo-elements. Then, we’ll use some JavaScript to add some transitional effects as we scroll down the page. 

Let’s get an idea of what we’ll be building (check out the larger version on CodePen).

1. HTML Markup

The markup we’ll use is pretty straightforward; a plain unordered list with a div element inside each of our list items. As we’re dealing with events along a timeline, we’ll give each list item a time element to display the year.

Additionally, we’ll wrap the whole thing within a section element with the class of timeline:

This gives us the following unstyled output:

2. Adding Initial CSS Styles

After some basic colors etc. (check out the upper section of the CSS in the pen below), we’ll define some structural CSS rules for the list items. We’ll also style the ::after  pseudo-element of these items:

I’ve removed the content within the list items to make this step clearer, giving us the following:

3. Timeline Element Styles

Now let’s style the div elements (we’ll call them “timeline elements” from now on) which are part of the list items. Again, we style the ::before pseudo-element of these elements. 

Also, as we'll see in a moment, not all divs share the same styles. Thanks to the :nth-child(odd) and :nth-child(even) CSS pseudo-classes, we’re able to differentiate their styles.

Have a look at the corresponding CSS rules below:

Then some styles for our odd elements:

Then finally the styles for our even elements:

With these rules in place (and our HTML once again complete with content) our timeline looks as follows:

The main difference between the “odd” and “even” divs is their position. The first ones have left: 45px while the second ones left: -439px. To understand the positioning of our even divs, let’s do some simple maths:

Width of each div + Desired spacing - Width of each list item = 400px + 45px - 6px = 439px

The second, less important difference is the generated arrow of their pseudo-element. That means, the pseudo-element of each of the “odd” divs has a left arrow, whereas the pseudo-element of each of the “even” divs displays as a right arrow. 

4. Adding Interactivity

Now that the basic structure of the timeline is ready, let’s figure out the new requirements: 

  • By default, the timeline elements (divs) should be hidden.
  • They should appear when their parent (list item) enters the viewport.

The first task is relatively straightforward. The second one, though, is a bit more complicated. We need to detect if the target elements (list items) are fully visible in the current viewport, then if that happens we reveal their child. To implement this functionality, we won’t use any external JavaScript library (e.g. WOW.js or ScrollReveal.js) or write our own complex code. Happily enough, there's a popular thread on Stack Overflow about this issue. So first let’s take advantage of the proposed answer to test whether an element is visible in the current viewport or not. 

Here’s the simplified function that we’ll be using:

Add Class When in View

Next, we add the in-view class to the list items which are visible in the current viewport. 

Note: we must test if they’re visible in the following cases:

  • When the page loads
  • As we scroll down

If needed, we can make some additional tests (such as when the size of the browser window changes).

In our example, here’s the code we use:

Now that we’ve added our JavaScript, if we reload the page, we should see a result similar to this: 

Hiding and Revealing

Let’s now revisit our initial requirement. Remember, by default, all divs should be hidden. To achieve this, we use the visibility and opacity CSS properties. Furthermore, we use the translate3d() function to move them 200px away from their original position. As long as their parent is in view, we’ll reveal them and remove the predefined offset. In this way, we create nice slide-in effects.  

Lastly, another small thing we’ll do when a li is within the viewport is to change the background color of its ::after pseudo-element.

The following styles take care of all that:

The following visualization shows the initial state of our timeline. Here you’re able to see the timeline elements because I've given them a touch of opacity just to illustrate where they’re initially located:

The default appearance of the timeline before the animationsThe default appearance of the timeline before the animationsThe default appearance of the timeline before the animations

And here’s the final state of the timeline:

The final appearance of the timeline after the animationsThe final appearance of the timeline after the animationsThe final appearance of the timeline after the animations

5. Customizing the Circles

By default, the ::after pseudo-element of each timeline element will look like a circle. However, let’s give some choices for customizing its initial appearance.

Most importantly, we’ll use the clip-path property to create some complex shapes. But happily enough, we won’t need to create them from scratch. We’ll take advantage of Clippy, a clip-path generator.

  • In case you want a star instead of a circle, add the timeline-clippy and timeline-star classes to the timeline like this:

The appearance of the timeline with stars instead of circlesThe appearance of the timeline with stars instead of circlesThe appearance of the timeline with stars instead of circles

  • In case you want a rhombus instead of a circle, add the timeline-clippy and timeline-rhombus classes to the timeline like this:
The appearance of the timeline with rhombuses instead of circlesThe appearance of the timeline with rhombuses instead of circlesThe appearance of the timeline with rhombuses instead of circles
  • In case you want a heptagon instead of a circle, add the timeline-clippy and timeline-heptagon classes to the timeline like this:

Clippy lets you create even more shapes, so be sure to check its site if you want something different.

If you still want to keep the circles, there's also the possibility to give them some infinite scale animation by using the timeline-infinite class like this:

Circles with infinite scale animation

Of course, you can also combine this animation with the aforementioned custom shapes like this:

Stars with infinite scale animation

Here are all the associated styles:

6. Going Responsive

We’re almost ready! The last thing we have to do is to make our timeline responsive. 

First, on what we’ll refer to as “medium screens” (>600px and ≤900px), we only make one small modification. Specifically, we decrease the width of the divs. 

Here are the rules that we have to change:

In such a case, the timeline looks like this:

On small screens, however (≤600px), all timeline elements look the same; there are no differences between the “odd” and “even” divs. Again, we have to overwrite some CSS rules:

On smaller screens the timeline looks as follows:

Note: on small screens, we use the vw unit to specify the width for the timeline elements. There aren’t any special reasons behind this approach. We could equally have used percentages or pixels.

Browser Support

The demo works well in most recent browsers and devices. On iOS devices, however, the timeline elements are always visible, instead of appearing as their parent enters the viewport. 

From my testing, I’ve seen that on those devices the window.innerHeight and document.documentElement.clientHeight properties don’t return the actual viewport height. Specifically, they return a much larger number. As a result of that inconsistency, all list items receive the in-view class when the page loads. 

Although it’s not a big deal (you may want the animations only on large screens), if you know more about this issue or you’ve seen it before, don’t forget to leave details via social media.


In this tutorial, we created a responsive vertical timeline. We’ve covered a lot of things, so let’s recap:

  • By using a simple unordered list and CSS pseudo-elements, we managed to build the main structure of our timeline. One downside to this approach though is that, as I’ve already mentioned in another article, CSS pseudo-elements aren’t 100% accessible, so keep that in mind.
  • We took advantage of a code snippet taken from a popular thread on Stack Overflow to test whether the list items are in view or not. Then, we wrote our own CSS to animate their child elements. Alternatively, we could have used a JavaScript library or written our own code.

I hope you enjoyed the tutorial and you’ll use this timeline as the basis for building something interesting. You may also like reading my follow-up tutorial Building a Horizontal Timeline With CSS and JavaScript – give it a try!

If you have any questions, let me know. As always, thanks a lot for reading!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.