How To Build A Responsive Timeline For A Webpage

by kleamerkuri
How To Build A Responsive Timeline For A Webpage

Build a responsive HTML CSS timeline with this step-by-step guide.

The objective is to build a responsive timeline with two display orientations for regular and mobile screen sizes.

This project is inspired by the Christensen Institute’s “The Evolution of Disruption” timeline. One of my close friends was recently hired by them (woohoo!) so I couldn’t help but look them up.

Currently their timeline visual is comprised of two images – a horizontal timeline version and a vertical timeline version – that cater to different screen sizes.

Our challenge is to transform the image into a responsive code-based component.

Why do so?

Because we can then transform it into a dynamic or, even, an animated object that enhances the user experience!

See the Pen Dynamic Timeline by Klea Merkuri (@thehelpfultipper) on CodePen.

1. Create the main line

Start off by creating the main line of the timeline using a single div called line.

<div id="line"></div>

Give #line some dimensions, so it can take shape, and background color which I declare universally as a CSS variable called –main.

:root {
    --main:  #455962;
}

* {
  margin: 0;
  padding: 0;
}

#line {
    width: 100%;
    max-width: 1190px;
    height: 4px;
    background-color: var(--main);
    margin: 0 auto;
    position: relative;
    top: 300px;
}

Note: I use the universal selector (the asterisk) to rid the document from default browser margins and paddings.

For the rounded ends of the line, I’ll use CSS selectors ::after and ::before to create pseudo-elements that are tiny circles.

#line::after {
    content: '';
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background-color: var(--main);
    position: absolute;
    right: -5px;
    top: -4px;
}

#line::before {
    content: '';
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background-color: var(--main);
    position: absolute;
    left: -5px;
    top: -4px; 
}

Note: I use the CSS position property to place the pseudo-elements at the ends of the line.

This is what the line looks like:

adding the main line of the timeline

2. Adding the first two alternating branches

One of the main challenges of this timeline project is building the asymmetrical, yet, balanced alternating branches.

I’ll start with the first two to help determine the positions and other properties necessary to bring the branches to life.

All branches will:

  • go inside of #line as I want to position them relative to the main line
  • contain two pseudo-elements – one for the short line making the 90-degree angle and the other for the circle at the very top/bottom of the line
two pseudo elements for each branch
Two pseudo-elements for each branch.

First, add the HTML for these first two branches:

    <div class="branch"></div>
    <div class="branch"></div>

Second, add the branch styles including the two pseudo-elements:

 .branch {
    width: calc(1190px/5);
    height: 130px;
    border-left: 3px solid var(--main);
    position: relative;
    bottom: 160px;
    left: 40px;
    display: inline-block;
}

 .branch::after {
    content: '';
    width: 15px;
    height: 2px;
    border-bottom: 3px solid var(--main);
    position: absolute;
    bottom: 0;
    left: -2px;
}

.branch::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--main);
    position: absolute;
    top: -4px;
    left: -5.5px;
}
including the first two timeline branches

Now, flip the second branch horizontally so it lies in the same place only beneath the main line.

Tip: Add background color to branch so you can see each branch’s full width and gain a better perspective of how to move them!

adding background color to branches
Add color to each branch to help position elements.

To do the flip, I’ll create a class called mirror-down which will transform the branch along the vertical axis using scaleY().

I’ll pass -1 as the value for scaleY() because I don’t want to resize the element, only alter the axial symmetry.

Note: scaleY(-1) will flip the element along a horizontal axis that crosses the element’s origin (in this case, the center).

Follow by bringing down the second branch by 30px (the maths are based on the height of the branch, 130px, plus the distance from the main line, 30px).

.mirror-down {
    transform: scaleY(-1);  
    top: 30px;
}
flipping an element horizontally mirror effect

3. Adding content to the first two branches

The next step is to add our content to the first two branches to work out the content’s respective positioning.

In each branch, I’ll add:

  1. a wrapper with a class of content
  2. a date in a wrapper with a class of year
  3. event information in a wrapper with a class of text

So the first two branches now look like so:

       <div class="branch">
            <div class="content">
                <p class="year">1971</p>
                <p class="text">Starbucks opens first store in Seattle’s Pike Place Market.</p>
            </div>
        </div>
        <div class="branch mirror-down">
            <div class="content">
              <p class="year">1988</p>
              <p class="text">Offers full health benefits to eligible full-and part-time employees, including coverage for domestic partnerships</p>
            </div>

        </div>
adding content to branches

Things end up skewed and it appears the positioning of the branches gets affected.

Let alone the mirrored branches’ text is upside down!

First, let’s correct the direction of the content for the mirror-down branch. Simply undo the transform using scaleY(-1).

.mirror-down .content {
  transform: scaleY(-1);
  bottom: 0;
}

Second, let’s address the skewed positioning of the branches. It seems to happen because of the content’s different lengths.

If #text is the same for both branches, our positioning isn’t affected.

But that won’t do, because we want the information to be different. No one’s going to make sure of the word equivalence of a sentence – that would be a nightmare.

To make it so the child content of the branch won’t affect the positioning of the branch, I’ll remove content from the flow of the page using absolute positioning.

Since content has a direct ancestor that is the branch, it will be positioned absolutely relative to the branch.

 .content {
    position: absolute;
    left: 30px;
    top: 0px;
    width: 200px;
}
adjusting positioning of branches with content

Related: CSS Visual Formatting: Floating, Positioning, & Layout

Styling the branch’s content

Increase the size of the year so it’s bigger relative to the info text and change the font family.

Add font-family: Helvetica, Arial, sans-serif to content. For the year, it looks like this:

.year {
  font-size: 1.2rem;
  font-weight: 300;
}

Then for text, slightly decrease the font size and set a margin at the top to add some space between the year and info text.

.text {
    font-size: 0.92rem;
    margin-top: 15px;
}

It’s looking good so far:

styling branch content

4. Including the other branches

Time to place all branches on the timeline!

Start by creating the branch wrappers without any content.

I went back to where I defined the width for the branch class and changed it to width: calc(1190px/10).

Since the content is positioned absolutely anyways, there is no need to fit it into its parent. This allows us to fit all branches on the given line.

Of course, if you decrease the screen size, things get distorted. But that’s something we’ll tackle a little later with media queries!

For now, you should have:

adding all branches on the timeline

Now add the content to each branch. Your complete HTML should look like so:

  <div id="line">
        <div class="branch">
            <div class="content">
                <p class="year">1971</p>
                <p class="text">Starbucks opens first store in Seattle’s Pike Place Market.</p>
            </div>
        </div>
        <div class="branch mirror-down">
            <div class="content">
              <p class="year">1988</p>
              <p class="text">Offers full health benefits to eligible full-and part-time employees, including coverage for domestic partnerships.</p>
            </div>

        </div>
        <div class="branch">
             <div class="content">
                 <p class="year">1991</p>
                 <p class="text"> Becomes the first privately owned U.S. company to offer a stock option program that includes part-time employees.</p>
             </div>
         </div>
        <div class="branch mirror-down">
            <div class="content">
                <p class="year">1999</p>
                <p class="text">Partners with Conservation International to promote sustainable coffeegrowing practices. </p>

            </div>
        </div>
        <div class="branch">
            <div class="content">
                <p class="year">2001</p>
                <p class="text">Introduces ethical coffee-sourcing guidelines developed in partnership with Conservation International.</p>
            </div>
        </div>
         <div class="branch mirror-down">
             <div class="content">
                  <p class="year">2006</p>                 
                  <p class="text">Launches the industry’s first paper beverage cup containing post-consumer recycled fiber.</p>

             </div>
         </div>
        <div class="branch">
            <div class="content">
                <p class="year">2008</p>
                <p class="text">Adopts new Mission Statement: “To inspire and nurture the human spirit – one person, one cup and one neighborhood at a time.”</p>
            </div>
        </div>
        <div class="branch mirror-down">
            <div class="content">
                <p class="year">2013</p>              
                <p class="text">Strengthens ethical sourcing efforts with coffee farming research and development center at Hacienda Alsacia in Costa Rica.</p>
            </div>
        </div>
        <div class="branch">
            <div class="content">
                <p class="year">2018</p>
                <p class="text">Commits to new environmental goals: phasing out disposable plastic straws by 2020 and operating 10,000 ‘Greener Stores’ globally by 2025.</p>
            </div> 
        </div>
    </div>
add all content to branches

Tip: Get rid of the background color you placed on the branches! It’s no longer necessary – the main use was for positioning and placement.

5. Inserting icons for each timeline event

Looking at the reference, the icons for when the timeline is horizontal are located at the very tops or bottoms of the branches.

You can almost say they’re exactly above the rounded end of each branch.

First, let’s decide what icons we’re going to use and where we’re going to find them. This step is dependent on your timeline theme/objective.

Since I’m doing a Starbucks-inspired timeline, I’ll try finding coffee-related icons on flaticon.

I downloaded the 128px-sized icons and hosted them on the repo for this project using Github pages.

Then I included the icon via an img tag within branch but before content. So the HTML code for each branch looks something like this:

         <div class="branch flip">
            <img src="https://thehelpfultipper.github.io/dynamic_timeline/timeline-icons/1971.png" alt="">
            <div class="content left">
                <p class="year">1971</p>
                <p class="text">Starbucks opens first store in Seattle’s Pike Place Market.</p>
            </div>
        </div>

        ...
inserting icons on timeline

Position the icons beside the rounded tips of the branches using absolute positioning.

I’m, also, going to resize them as they’re a tad too large for my liking.

img {
  position: absolute;
  top: -135px;
  left: -40px;
  transform: scale(0.6);
}

Things are looking way better already! If you disregard the fact I messed up the 1988 icon (now corrected!), there’s only one thing left to do with the icons at this time.

In particular, that thing applies to the icons in the mirror-down branches which are upside down.

Though we can correct the orientation of those icons using our trustee scaleY(), this will overwrite the scale(0.6) applied to img for the images found in the mirror-down wrappers.

What to do?

Merely redefine the scale factor in the transform property of the mirror-down images like so:

.mirror-down img {
  transform: scale(0.6) scaleY(-1);
}

Note: I brought down #line by 400px as it was far too close to the top.

correcting mirrored icons

6. Creating the vertical timeline for smaller screens

Time for media queries to make this gorgeous-looking timeline responsive.

Breakpoints for horizontal timeline

My first step is to create two breakpoints for the horizontal timeline.

On each breakpoint, I will decrease the size of the timeline (and so of all its children components) by a factor of 0.1.

This is because I don’t wish to have a vertical timeline immediately – instead, I’ll gradually get there.

@media (max-width: 1435px) {
  #line {
    transform: scale(0.9);
  }
}

@media (max-width: 1256px) {
  #line {
    transform: scale(0.8);
  }
}

Tip: You can create more breakpoints for the horizontal timeline keeping in mind the readability.

If things become too crowded or, during proportioned resizing, too small, then you know it’s time to go vertical.

Going from horizontal to vertical

On screens 1255px or smaller, the main line will rotate 90 degrees hinging on the left side of #line.

I specify the origin of the rotation using the CSS transform-origin property. Then I move the line at the center of the page.

@media (max-width: 1255px) {
  #line {
        transform: rotate(90deg);
        transform-origin: left;
        top: 40px;
        left: 50%;
    }
}

Note: All other styles for screens 1255px or smaller are going to go into the defined media query above!

The timeline is now turned, which doesn’t do us much good mind you, but it’s a step forward nonetheless.

turning the horizontal timeline vertically

Now let’s observe the vertical timeline version of the reference. Make note of:

  • events at the top go on the left side of the main line
  • events at the bottom go on the right side of the main line
  • the icons move beneath the main content of the branches
  • the branches become short and attached to the main line

We’ll go step by step (no matter how much DM rolls his eyes on hearing this) through each of these points to make them happen!

1) Moving top branches to the left

In the media query, I’ll define a class called flip that will get assigned to each of the branches placed at the top of the horizontal line.

This class will solely be responsible for moving the branch to the other side of the vertical timeline.

    .flip {
        transform: scaleY(-1);
    }

Note: Don’t forget to add the class only to the top branches!

Not going to lie, the result made me cringe – it certainly doesn’t look pretty.

flipping the top branches

The top branches don’t move, they only flip. They’re in the wrong location though and the content as well as icons also flipped. Go figure.

2) Moving the bottom branches to the right

Since we’re already horrified, might as well do the same (yet opposite) thing to the bottom branches.

I define a class in the media querry called turn that flips the bottom branches to the right.

    .turn {
        transform: scaleY(1);
    }

3) Changing the branches

Let’s tackle the branches. Two objectives here:

  1. shorten and attach them to the main vertical line
  2. make the rounded edges a bit larger

I don’t define any new classes here – only affecting existing ones. Yay!

    .branch {
        bottom: 0px;
        height: 25px;
    }

    .mirror-down {
        top: -22px;
        left: -70px;
    }

    .branch::after {
        content: '';
        border: none;
    }

    .branch::before {
        content: '';
        width: 10px;
        height: 10px;
        left: -6px;
    }

Word of warning, the content of each branch is absolutely messed up. If yours are overlapping, you’re good…don’t panic.

4) Adjusting the branch content orientation

There’s going to be lots of flipping and turning here. So much, in fact, that left, right, top, and bottom properties will be moving in directions you might not expect.

Be prepared for some trial and error.

I’ll begin by creating a class called left that will be assigned to each of the left-ward branch content.

Then, I’ll do the same thing with a class called right that will be assigned to each of the right-ward branch content.

The properties that the left and right classes affect differ based on the requirements of rotation and placement for each side.

    .text {
        font-size: 1rem;  
    }

    .left {
        left: 4px;
        top: -220px;
        transform: scaleY(-1) rotate(-90deg);
        transform-origin: top left;
        width: 220px;
    }

    .right {
        left: -10px;
        top: -20px;
        transform: rotate(-90deg)!important;
        transform-origin: top left;
    } 

Note: I gave left a width here to adjust the content width in the given space.

correcting orienation of content in vertical timeline

Looking good. Know what’ll look even better? Fixing those icons, that’s what 😏

Applying the same logic of rotation and placement, I’ll hit the icons for each side.

    .flip img {
      left: 110px;
      top: -200px;
      transform: rotate(-90deg) scaleX(-1) scale(0.6);
    }

    .turn img {
      left: 100px;
      top: -160px;
      transform: rotate(-90deg) scaleX(-1) scale(0.6);
    }
correcting icons on vertical timeline

5) Tying loose ends for even smaller screens

Currently we’re running into a sizing issue with the screen that arises primarily due to the dynamic width of #line.

Recall, #line takes on full-width based on a max-width of 1190px. So it’s fluid, it adjusts as the screen resizes, throwing off our placements.

The solution is to make the width static at 1190px for screens 1257px or smaller – add width: 1190px to the #line properties defined in the media query.

Aha! No more problemo. Though I do want to create a breakpoint around 652px and scale the vertical timeline down a bit.

@media (max-width: 652px) {
  #line {
      transform: rotate(90deg) scale(0.8);
      transform-origin: left;
      top: 40px;
      left: 50%;  
      width: 1190px;
    }
}

Tip: Remember to carry over the defined styles in the previous media query! Those styles only exist in the defined media query for a vertical line.

7. Extra: Adding a header and footer

This part is optional, but I figured since the theme is Starbucks I might as well tie the timeline design to it.

Header with title and caption

As a header, I’m including a title and caption before #line like so:

<header id="title">
  <h2>STARBUCKS</h2>
  <p>Through time</p>
</header>

Add a background color to #title with some padding and center everything. Give the heading and caption some styles as well.

#title {
  background: #D4E9E2;
  padding: 40px;
  text-align: center;
  font-family: Helvetica, Arial, sans-serif;
}

#title h2 {
  font-weight: 700;
  color: #203931;
  font-size: 1.8em;
}

#title p {
  color: #2C4740;
  margin-top: 5px;
  letter-spacing: .102rem;
}

Meanwhile, as a footer, I’m including icon attributions and the Starbucks logo.

Since I have nine icons, I envision one row with three columns, each column containing three attributions.

I’ll be using flexbox to create the illusion of a table instead of an actual table following this row and column flexbox layout by Andrew Stuntz.

So my HTML looks like so:

<footer>
  <div class="row">
    <div class="column">
        <a href="https://www.flaticon.com/free-icons/cafe" title="cafe icons">Cafe icons created by Smashicons - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/coffee" title="coffee icons">Coffee icons created by Freepik - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/environment" title="environment icons">Environment icons created by iconixar - Flaticon</a>
    </div>

    <div class="column">
      <a href="https://www.flaticon.com/free-icons/coffee" title="coffee icons">Coffee icons created by Freepik - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/coffee" title="coffee icons">Coffee icons created by Smashicons - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/coffee" title="coffee icons">Coffee icons created by srip - Flaticon</a>
    </div>

    <div class="column">
      <a href="https://www.flaticon.com/free-icons/people" title="people icons">People icons created by Freepik - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/teamwork" title="teamwork icons">Teamwork icons created by Becris - Flaticon</a>

<a href="https://www.flaticon.com/free-icons/renewable-energy" title="renewable energy icons">Renewable energy icons created by Eucalyp - Flaticon</a>
    </div>
  </div>
</footer>

Note: I haven’t defined any styles yet. Due to different positionings of other elements on the page, you’ll notice the footer appears right under the header.

adding a footer with attributions

Styling the footer and attribution links

I’ll start by positioning the footer towards the bottom of the page.

The distance from the top (or bottom) will vary based on screen size. For instance, the top-down distance is different when the timeline is horizontal vs when it’s vertical.

For the horizontal timeline, I will set the distance to 640px from the top.

footer {
  position: relative;
  top: 640px;
  padding: 60px 20px 20px 20px;
  background-color: #D4E9E2;
}

Then, inside the 1255px media query, I’ll set the footer 1350px from the top.

    footer {
      position: relative;
      top: 1350px;
    }

Note: The distances might vary if you’ve been setting different dimensions. Play around and find the best one that suits your project!

Since we settled the footer position, let’s move on to the attribution links.

First, get rid of the default link styles like underline and color. Then add very slight padding to the bottom and give each link a relative position.

footer a {
  text-decoration: none;
  color: #2C4740; 
  padding-bottom: 2px;
  position: relative;
  margin-bottom: 0.4rem;
}

Why?

Because I want to make them a lil’ fancy. On hover, I’d like an underline to grow from the middle of each link.

How to animate a link using CSS

To get the animated link shown above, I’ll use the CSS ::after pseudo-element on each link.

The pseudo-element I’m creating is a rectangle that resembles an underline spanning the width of the link.

We’re faking a bunch of things here (LOL), but it’s all for creative genius 🙂

footer a::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  height: .08rem;
  width: 100%;
  background-color: #2C4740;
  transform: scale(0);
  transform-origin: center;
  transition: transform .375s;
}

Giving the pseudo-element scale(0) makes it invisible to the eye and that’s on purpose.

In order to create the “grow from the middle” animation, I’m basically shrinking the pseudo-element from the middle on render. Then, I define a transition on the transform over 375 milliseconds.

Nothing will happen until defining the changing states of the transform.

When hovering over the link, change the cursor into a pointer and grow the pseudo-element to full-width.

footer a:hover {
  cursor: pointer;
}

footer a:hover::after {
  transform: scale(1);
}

And voilà! Animated link done 🙌

Setting rows and columns with flexbox

Though I created the row and column wrappers in the HTML, I haven’t defined any styles for them yet.

Note: I’ll add some background color to the row and columns to illustrate what will happen.

The row (yellow background) is going to span full width and will contain certain flex properties that will apply to its children, like wrap and direction.

.row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-evenly;
  width: 100%; 
}
creating columns with flexbox

Meanwhile, the columns (pink background) will wrap as the screen gets smaller. While the columns’ content is stacked using flex-direction as column.

.column {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
} 
wrap columns flexbox

In the 1255px media query, give each column a bottom margin and center everything on the page.

    .column {
      margin-bottom: 30px;
      width: 100%;
      align-items: center;
    } 

Tip: Since we’re using a single font-family for the entire project, font-family: Helvetica, Arial, sans-serif, delete the individually defined instances and assign the property to the universal selector!

Include a logo and copyright

Time to add a sub-footer to the footer – isn’t that fun?

But before moving onto the sub-footer, I want to add a triangular bullet before each link.

footer a::before {
  content: '\2023  ';
  font-size: 120%;
}

Now, for the sub-footer:

  <div class="row sub-foot">
      <div class="column img-col">
          <img src="https://thehelpfultipper.github.io/dynamic_timeline/starbucks-logo-1.png" alt="starbucks-logo" id="logo-img">
    </div>
     <div class="column copy-col">
       <p id="copy">© 2022, Starbucks Timeline by THT. All Rights Reserved.</p>
     </div>     
  </div>

I’ll use the footer layout of columns and rows that I created earlier. But I know things won’t go smoothly, in part due to the dimensions of the symbol.

So, I assign additional classes to the row and columns to use for adjustments.

adding a subfooter

The first adjustment applies to the row. All it does is:

  • place the two columns on either end of the row
  • decrease the bottom margin
.sub-foot {
  justify-content: space-between;
  margin-bottom: 5px;
}

Seems we still have “stacked” columns as the copyright paragraph lies beneath the logo image.

Nothing is telling the containers to stack – the culprit is the width of the symbol. To account for it:

  • set a width for img-col to half of that of the row
  • make the image fit its column
  • shift the image to the left edge of the screen
.img-col {
  width: 50%;
}

#logo-img {
  width: 100%;
  height: auto;
  position: relative;
  left: -200px;
}
adjusting image in flexbox

Align the copyright paragraph to the bottom of its column and decrease the size. For alignment, I’ll affect the copy-col by setting a flex display.

.copy-col {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  font-size: 0.9em;
}

So the sub-footer for our horizontal timeline version looks like this:

horizontal timeline footer
Mobile sub footer

Make adjustments to the sub-footer for smaller screens (aka our vertical timeline version).

By the row’s previous settings, the columns (children of the row) will wrap so we get the stacking on smaller screens that we want.

I only need to make a few adjustments to the media queries:

  • decrease the size of the logo image
  • decrease the bottom margin of the sub-footer
  • correct alignment by centering the sub-footer content

Note 📝

Without the stylings we applied above to sub-footer, we get basically all the adjustments listed with the exception of the logo size!

If we’re smart about this, instead of undoing lots of styles, we can define a media query for screens 1256px and larger that contains the sub-footer styles.


@media (min-width: 1256px) {

  /* subfooter */
  .sub-foot {
    justify-content: space-between;
    margin-bottom: 5px;
  }

  .img-col {
    width: 50%;
  }

  #logo-img {
    width: 100%;
    height: auto;
    position: relative;
    left: -200px;
  }

  .copy-col {
    display: flex;
    flex-direction: row;
    align-items: flex-end;
    font-size: 0.9em;
  }
}

This way, the only thing left to do is resize the logo image on smaller screens and reduce the bottom margin. We don’t have to deal with alignment again – whoop!

Add padding-bottom: 0 to the footer in the 1255px media query and define:

    #logo-img {
      width: 80%;
    }

    .copy-col {
      font-size: 0.9em;
    }
vertical timeline footer

Lastly, in the 652px media query, adjust the footer’s position and the logo image size.

 footer {
      position: relative;
      top: 1180px;
      padding-bottom: 0;
  }

  #logo-img {
      width: 100%;
  }

Extra-extra: One-line animation

If you made it to this part, congrats – you’re awesome.

As a little extra-extra I’ll show you how to make this responsive, yet, static timeline up a notch using a one-line animation!

Go to where you defined your branch styles and add transition: all 1.5s ease. Then resize and bask in the glory of code 🙂

Note: All we’re doing is transitioning the branch styles once the screen size hits our vertical timeline media query (which is where we re-touch branch for the first time following initial declaration).

Of course, you can add more nuanced animations. Be mindful, however, to keep things light . . . unless you want to go all out, in which case, don’t let us hold you back.

Find the full source code on our GitHub page.

See ya on another project 😉

Related Posts