Creating a Progress Bar with CSS
This week at Goodsmiths we launched our redesigned 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.
Design
First, here’s a look at the design mockups for the intended appearance:
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 cssarrowplease.com 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:
Notes:
- 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.
Cleanup
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?
|
Tore |
Thanks for explaining the border part so good with graphical examples
Susanne Kappler |
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):
http://codepen.io/skappler/pen/NNzMYw/
Cheers!
Brendan |
thank you so much. Learned a lot from this.
Adam Sepoct |
Very manipulative. Very nice
RKM |
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
Eric |
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.
Eric
Sohail |
Hey, thanks for this! Made my life much easier. I styled up the li elements to my liking.
Prasanna |
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
Prasanna
Eric |
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: http://jsfiddle.net/pBFAx/32/
Eric
John Evans |
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
http://blog.thehippo.de/2012/04/programming/do-a-loop-with-less-css/
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. :-)
John
Eric |
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.
Eric