Creating a Progress Bar with CSS

Posted on June 26, 2013

This week at Goodsmiths we launched our redesigned checkout process.

Screenshot of first step of checkout process.

First step in the checkout process.

Because the progress bar (across the top) was one of the more interesting pieces to create, I thought I’d walk through the CSS used to construct it.


First, here’s a look at the design mockups for the intended appearance:

Mockup of step 1

Mockup of step 1.

Mockup of step 2

Mockup of step 2.

Hopefully, the final result looks similar. :)

Initial HTML

As always, the goal is to have semantic HTML without too much extra markup. A progress bar is an ordered sequence, so let’s start with HTML’s ordered list element:

Note that I did not include the step number in the LI contents — it is expressed semantically through the list structure.

I’ve also added some CSS classes for future styling. Of course, you could use different CSS selectors instead of classes, but I think they are useful here.

Horizontality & Basic Formatting

First, let’s turn this into a horizontal list using the standard CSS declarations. I will be writing the code in SCSS, because it is so much better than vanilla CSS.

(We’ll be needing the $padding value for other calculations soon.)

Let’s also add some basic formatting of the text and colors:

Again, we’ll need the $line_height for future calculations.

Finally, let’s include the numbering on the items using the :before pseudo-element and CSS counters. I’m going to add a <span> tag around the list contents and apply the number to that. (I’ll explain why in a moment.)

Okay, that was the easy part. There shouldn’t be any surprises here if you work with CSS regularly.

CSS Arrows

Now let’s add the chevrons (arrows) to the list items. There’s a well-known trick for creating triangles in CSS. For those who don’t know, CSS allows you to set a different color for the border on each side of a box element. Here’s a square element with wide borders:

Notice the diagonal lines at the corners? Here’s what happens when you make the box zero pixels in size and increase the border width:

Now, let’s make all but one side transparent:

Neat, huh? Because the effect requires the element to be zero width/height, it has to be used on a throwaway element, or more commonly, a pseudo-element like :after. You can use a generator like to do this for you.

Appending the Arrows

Okay, now let’s append some arrows to our list items.

Some notes:

  • I’m calculating $arrow_size from the height of list item, because the arrow should extend the full height. Unfortunately, the downside of using the CSS Arrow method is that the width of the arrow can’t be adjusted… it will always be half of its height.
  • For best rendering results, the height of the list items (and thus the arrow) should be an even number. Some browsers (like Chrome) have weird ideas of rounding.
  • Unfortunately, elements that come later in the HTML are rendered later, so they will overlap preceding elements. This is undesirable in our case, because the arrows need to overlap the items that follow. To get around this, we give earlier items a higher z-index.

Prepending the Cutouts

If the arrows just needed to overlay the following item, we could just add some padding to the front of each item and call it a day. But we want a bit of gap between the items. For this, we need to add a cutout to the front of each item and shift it right.

Creating a cutout is the same as creating a triangle — just invert the transparency:

Now if we prepend a triangle cutout to the front of each item:


  • This is where the semantically-empty span element comes into play. Because each item has an arrow and cutout, an extra (pseudo-)element is needed. You may be able to place it on :before instead of another :after, but an extra element is needed anyway for the list numbering.
  • If you wanted to save space, you could overlay the cutout on its list item and use the page background color instead of transparent. Prepending it is a bit cleaner, and works with background images or gradients as well, at the cost of wider list items.
  • I added 5px to the left margins for the gap. You can tweak this to whatever value you want.
  • I added extra right padding to the span elements to keep the text centered.


Finally, we don’t want arrows at the beginning or end of our list, so let’s remove those:

Voilà! Note that IE8 doesn’t support :last-child, so you’ll need a polyfill or different selector to support that version.

From here you can give the list items equal widths, and center the whole thing on the page.

Comments? Do you know a better way or have improvements?

Leave a Reply

11 Responses to Creating a Progress Bar with CSS



  2. Hi Eric,

    Really nice tutorial, thanks for posting!

    I wanted to point out that I do believe it’s possible to adjust the width of the arrow using your method.

    Instead of using “border-width: $arrow_size;” on the arrow, you can use “border-width: $line_height;” and then set the border-left-width as wide or shallow as needed.

    See the applied code here (Line 50):


  3. Hi John,

    I was going through this article and its very good liked it :). Just wanted to know is it possible to give little different look for current step say more height and different border color

    • RKM,

      I haven’t tried it myself, but you should be able to tweak the formatting of the “.current” class in the CSS to suit your needs.


  4. Hi Eric,

    Many Thanks for the tutorial. Really useful and I am looking to use your code (I hope this is okay?).

    Can you please advice on how to center the progress bar?

    Maybe this is something very basic but I am very new to css, your help would be much appreciated.

    Many Thanks


    • Hi Prasanna,

      Yes, feel free to adapt this code for your own purposes. If you can add a comment somewhere that links back to this page, that would be appreciated, but it’s not required.

      To center the progress bar, I used the trick of setting the list’s display property to “table” (display: table). Then you can center it using “auto” for the left and right margins. You can see the updated code using this method here:


  5. Cheers for this.

    I was looking for a tutorial to be honest. I know how to do triangles and the rest and if I had the time I’d be able to sit down and figure it all out, put all the steps together.

    So with your code I can copy and paste.

    Although I normally use scss I have to use less.js on this one. On most of the things it’s just a matter of changing the $ for @ but the one thing that less.js doesn’t do out of the box is for loops.

    If anybody else is looking at these comments in the hope of finding an answer to the less.js for loop problem I found this link

    which will hopefully help me, if not I’ll simplify and de-modularise the code to make it more specific to my scenario.

    Thank you again for the code. :-)

    • Hi John!

      Glad you found it useful. I haven’t used LESS much, but it’s good to know that this can be duplicated somewhat easily for that language.