CSS3 Progress Bars

Feb 24 2011
39

I made some progress bars. They look like this:

View Demo   Download Files

They use no images, just CSS3 fancies. Like a good little designer always does, they fall back to totally acceptable experience. Here's what they look like in Opera 11 which supports some of the CSS3 used here but not all.

As you might imagine, in browsers that support no CSS3 at all will look similar to the above, only even more simplified.

HTML Base

The bar itself will be a <div> with a class of meter. Within that is a <span> which acts as the "filled" area of the progress bar. This is set with an inline style. It's the markup which will know how far to fill a progress bar, so this is a case where inline styles make perfect sense. The CSS alternative would be to create classes like "fill-10-percent", "fill-one-third" or stuff like that, which is heavier and less flexible.

The basic:

<div class="meter">
	<span style="width: 25%"></span>
</div>

Start of CSS

The div wrapper is the track of the progress bar. We won't set a width, so it will stretch as wide as it's parent as a block level element does. You could though. Height is also arbitrary. It's set at 20px here but could be anything. We'll round the corners in as many browsers as we can and set an inset shadow to give it a hair of depth.

.meter {
	height: 20px;  /* Can be anything */
	position: relative;
	background: #555;
	-moz-border-radius: 25px;
	-webkit-border-radius: 25px;
	border-radius: 25px;
	padding: 10px;
	-webkit-box-shadow: inset 0 -1px 1px rgba(255,255,255,0.3);
	-moz-box-shadow   : inset 0 -1px 1px rgba(255,255,255,0.3);
	box-shadow        : inset 0 -1px 1px rgba(255,255,255,0.3);
}

Then span inside will be the fill in part of the progress bar. We'll make it display as a block with 100% height, so it stretches to fit whatever room it has. We'll then use a bunch of CSS3 to give it gradient look and round it's corners.

.meter > span {
	display: block;
	height: 100%;
	   -webkit-border-top-right-radius: 8px;
	-webkit-border-bottom-right-radius: 8px;
	       -moz-border-radius-topright: 8px;
	    -moz-border-radius-bottomright: 8px;
	           border-top-right-radius: 8px;
	        border-bottom-right-radius: 8px;
	    -webkit-border-top-left-radius: 20px;
	 -webkit-border-bottom-left-radius: 20px;
	        -moz-border-radius-topleft: 20px;
	     -moz-border-radius-bottomleft: 20px;
	            border-top-left-radius: 20px;
	         border-bottom-left-radius: 20px;
	background-color: rgb(43,194,83);
	background-image: -webkit-gradient(
	  linear,
	  left bottom,
	  left top,
	  color-stop(0, rgb(43,194,83)),
	  color-stop(1, rgb(84,240,84))
	 );
	background-image: -webkit-linear-gradient(
	  center bottom,
	  rgb(43,194,83) 37%,
	  rgb(84,240,84) 69%
	 );
	background-image: -moz-linear-gradient(
	  center bottom,
	  rgb(43,194,83) 37%,
	  rgb(84,240,84) 69%
	 );
	background-image: -ms-linear-gradient(
	  center bottom,
	  rgb(43,194,83) 37%,
	  rgb(84,240,84) 69%
	 );
	background-image: -o-linear-gradient(
	  center bottom,
	  rgb(43,194,83) 37%,
	  rgb(84,240,84) 69%
	 );
	-webkit-box-shadow:
	  inset 0 2px 9px  rgba(255,255,255,0.3),
	  inset 0 -2px 6px rgba(0,0,0,0.4);
	-moz-box-shadow:
	  inset 0 2px 9px  rgba(255,255,255,0.3),
	  inset 0 -2px 6px rgba(0,0,0,0.4);
	position: relative;
	overflow: hidden;
}

Thems the basics.

Other Colors

Let's make it as easy as possible to change the color. Just add a class name of "orange" or "red" to the div wrapper and the color will override.

.orange > span {
	background-color: #f1a165;
	background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #f1a165),color-stop(1, #f36d0a));
	background-image: -webkit-linear-gradient(top, #f1a165, #f36d0a);
        background-image: -moz-linear-gradient(top, #f1a165, #f36d0a);
        background-image: -ms-linear-gradient(top, #f1a165, #f36d0a);
        background-image: -o-linear-gradient(top, #f1a165, #f36d0a);
}

.red > span {
	background-color: #f0a3a3;
	background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, #f0a3a3),color-stop(1, #f42323));
	background-image: -webkit-linear-gradient(top, #f0a3a3, #f42323);
        background-image: -moz-linear-gradient(top, #f0a3a3, #f42323);
        background-image: -ms-linear-gradient(top, #f0a3a3, #f42323);
        background-image: -o-linear-gradient(top, #f0a3a3, #f42323);
}

Candystriping

We can get a cool striped effect by adding another element on top of that span and laying a repeated CSS3 gradient over it. Semantically this is best achieved with a pseudo element, so let's do it that way. We're going to absolutely position it over the exact area of the span (which already has relative positioning) and then round the corners the same so the stripes don't stick out weird.

.meter > span:after {
	content: "";
	position: absolute;
	top: 0; left: 0; bottom: 0; right: 0;
	background-image:
	   -webkit-gradient(linear, 0 0, 100% 100%,
	      color-stop(.25, rgba(255, 255, 255, .2)),
	      color-stop(.25, transparent), color-stop(.5, transparent),
	      color-stop(.5, rgba(255, 255, 255, .2)),
	      color-stop(.75, rgba(255, 255, 255, .2)),
	      color-stop(.75, transparent), to(transparent)
	   );
	background-image:
		-webkit-linear-gradient(
		  -45deg,
	      rgba(255, 255, 255, .2) 25%,
	      transparent 25%,
	      transparent 50%,
	      rgba(255, 255, 255, .2) 50%,
	      rgba(255, 255, 255, .2) 75%,
	      transparent 75%,
	      transparent
	   );
	background-image:
		-moz-linear-gradient(
		  -45deg,
	      rgba(255, 255, 255, .2) 25%,
	      transparent 25%,
	      transparent 50%,
	      rgba(255, 255, 255, .2) 50%,
	      rgba(255, 255, 255, .2) 75%,
	      transparent 75%,
	      transparent
	   );
	background-image:
		-ms-linear-gradient(
		  -45deg,
	      rgba(255, 255, 255, .2) 25%,
	      transparent 25%,
	      transparent 50%,
	      rgba(255, 255, 255, .2) 50%,
	      rgba(255, 255, 255, .2) 75%,
	      transparent 75%,
	      transparent
	   );
	background-image:
		-o-linear-gradient(
		  -45deg,
	      rgba(255, 255, 255, .2) 25%,
	      transparent 25%,
	      transparent 50%,
	      rgba(255, 255, 255, .2) 50%,
	      rgba(255, 255, 255, .2) 75%,
	      transparent 75%,
	      transparent
	   );
	z-index: 1;
	-webkit-background-size: 50px 50px;
	-moz-background-size:    50px 50px;
	background-size:         50px 50px;
	-webkit-animation: move 2s linear infinite;
	   -webkit-border-top-right-radius: 8px;
	-webkit-border-bottom-right-radius: 8px;
	       -moz-border-radius-topright: 8px;
	    -moz-border-radius-bottomright: 8px;
	           border-top-right-radius: 8px;
	        border-bottom-right-radius: 8px;
	    -webkit-border-top-left-radius: 20px;
	 -webkit-border-bottom-left-radius: 20px;
	        -moz-border-radius-topleft: 20px;
	     -moz-border-radius-bottomleft: 20px;
	            border-top-left-radius: 20px;
	         border-bottom-left-radius: 20px;
	overflow: hidden;
}

I first saw and snagged this idea from Lea Verou.

Animating Candystripes

Only Firefox 4 can animate pseudo elements, and only WebKit can do keyframe animations. So unfortunately we're between a rock and a hardplace in terms of animating those stripes. If we're set on it, let's add an additional span and then WebKit animate that.

<div class="meter animate">
	<span style="width: 50%"><span></span></span>
</div>

The span will be exactly the same as the pseudo element, so we'll just use the same values...

.meter > span:after, .animate > span > span {

... and avoid doubling up:

.animate > span:after {
	display: none;
}

We'll move the background position as far as the size of it:

@-webkit-keyframes move {
    0% {
       background-position: 0 0;
    }
    100% {
       background-position: 50px 50px;
    }
}

And call that animation:

.meter > span:after, .animate > span > span {
  -webkit-animation: move 2s linear infinite;
}

Might as well leave the animation tied to the pseudo element too, so as soon as WebKit starts supporting that, it will work.

Animating the Filled Width

Unfortunately you can't animate to an auto or natural width, which might let us animate from a forced zero to the inline style.

@-webkit-animation expandWidth {
   0% { width: 0; }
   100% { width: auto; }
}
Update 1/25/2012: Turns out you CAN animate to an inline style. Just omit the "to" or "100%" ending value in the @keyframe

I've submitted it to major browsers bug trackers just to push it a long a little, but for now, unsupported. Instead, let's do it with jQuery. Measure the original width, force it down to zero, then animate back up:

$(".meter > span").each(function() {
	$(this)
		.data("origWidth", $(this).width())
		.width(0)
		.animate({
			width: $(this).data("origWidth")
		}, 1200);
});

And done:

View Demo   Download Files

Others Takes

HEY?! What about HTML5?

Dude my dude. HTML5 has features specifically for this. <progress> and <meter>! Yep, it does, but here's the rub. These elements have very specific appearance already applied to them. By default, they look like progress bars used elsewhere on the platform you are on. Like this on Mac:

You can turn off that default styling like this:

progress {
   -webkit-appearance: none;
}

That'll allow you to remove the glossly thing going on with that default styling, but it's still pretty limited as to what you can do. You can change the progress bar inside like this:

progress::-webkit-progress-bar-value {
  -webkit-appearance: none;
  background: orangered;
}

...and that's fairly limited in what you can do with it afterward as well. To make things worse, things are very different across browsers, even between different WebKit browsers. Pseudo elements also work inconsistently. I hate to leave things hanging like this, but this is really a topic for another time. Suffice it to say, for these particular progress bars, the div/span thing is the ticket for now.

Subscribe to The Thread

  1. Whoa, these are awesome!

    The rounded corners for the containers seem to be buggy in chrome/xp.

  2. Arturs

    Thank you, I will be useful, cool

  3. Just wanted to leave a comment to say thanks for the mention of my own attempts at creating progress bars.

    I found that jQuery is the only way (currently) to animate things in terms of progression, but CSS3 would really be a much nicer way to do it once browser support actually allows for it.

  4. hello i hava a website http://www.jkrt.org
    want to do friendlink with you website

  5. You featured my progress bars! How cool!

  6. Funny css3 tutorial,but I don’t know where can show it.

  7. Neat, neat, neat!! I don’t know much about this, so this is cool!! A well constructed process bar turns me on.

  8. very neat results

  9. aw aw aw…so smooth…good color

  10. Cool colors. Thanks for sharing.

  11. Hey Chris,

    Thanks for posting my implementation and the retweet the other night! I appreciate all your hard work, I’ve learned a bunch from you over the past couple years. Cheers!

  12. Very cool. Thanks!

  13. This is killer! Nice work Chris!

  14. Just wanted to say that just under the paragraph just after the “HTML Base” heading there should be a highlighted span which doesn’t appear (at least on Chrome, win XP).

  15. Those border-radius props can be condensed into shortform. Only Safari <5 and iOS <3 (prior to WebKit 532.5) have a problem with unique corner radii.

    So that can become:

    -moz-border-radius: 8px 20px 20px 8px;
    -webkit-border-radius: 8px 20px 20px 8px;
    border-radius: 8px 20px 20px 8px;

    and if you want iOS 3.1 and Saf4 support:

    -webkit-border-top-right-radius: 8px;
    -webkit-border-bottom-right-radius: 8px;
    -webkit-border-top-left-radius: 20px;
    -webkit-border-bottom-left-radius: 20px;

    You can add that top the top of it.

    • There’s also no need for border-radius on the pseudo-element / nested span because overflow is set to hidden on the “parent” element.

    • That one doesn’t actually pan out Nicolas, if you leave on the hidden overflow but turn off the border radiusii, you get stick outs. See: http://cl.ly/4rTw I’d call that a bug.

  16. Gordon

    I think it would be better when the progress bar reaches 100%, its right end can become round (just like left end) instead of a square with round corners.

  17. Wonderful!!

    Thanks!

  18. Wow chris sheer genius i really like it!

  19. Jasper

    Is there any way to slow down the animation?

    • Jasper

      nevermind! was a muppet and reduced the value in JS rather than increase it.

  20. Scott

    (Don’t seem to be able to reply to others on mobile site)

    @Paul, did the border radius spec change? It always used to be that multiple values created elliptical corners (though I personally never understood why they did it that way).

    Anyway, what is the need for different corner radii in this example? Shouldn’t all 4 corners be the same?

  21. awesome

    can the border-radius increases as the bar moving to the end?

  22. The HTML part of this source code isn’t W3C compliant (XHTML -HTML5). You can’t specify style attribute on element must be in the stylesheet.

    Nice use of CSS, in all case, the final result look awesome and fully skinnable.

    Nico

  23. I’ve been playing around with progress bars and came up with one funny thing to mark the progress bar that reached 100%.
    If you use percentage to specify the width


    .meter > span[style*="100"] { background: gray}

    or if you specify width with pixels


    .meter > span[style*="470"] { background: gray}

    of course this selector is vulnerable to numbers showing in other inline styling so use with caution.

  24. this is really cool! i always thought that loading images or sequences were some kind of programming. it was like a science i didn’t understand or something.

    it’s nice when it’s explained for the dummy! thanks

  25. Ali Bahrami

    OMG,
    That’s awesome , Chris.

    I thought I know a little about CSS but I guess I know nothing about it :P

    Wish you better jobs ^_^

  26. It is good news for my wordpress (style)

  27. While all of this stuff is super cool, they are not going to be used until CSS3 and HTML5 have become a standard. I wish we cool jump ahead a few years in terms of HTML standards….too many dang people are still running IE7/IE8. AGGGHH

  28. Andrew

    Very cool but you gotta have the animation running the other way man! That way the progress bar looks like it’s making more progress than it is! This way it looks like it’s slower.

  29. Excellent job done. Can easily be used

  30. I’ve adjusted the code somewhat. I’ve added a span to the markup that holds the percentage.


    <div class="meter-wrapper">
    <div class="meter"><span style="width:35%"></span></div>
    <span class="progress">35%</span>
    </div>

    The progress span gets hidden on pageload and is then faded in when the bar animation is finished.

    I also needed the progress bar to resize with the container when the browser gets resized. jQuery’s width() only returns px, so I needed a little workaround.


    jQuery('.meter-wrapper .progress').hide();
    jQuery(".meter > span").each(function() {
    var w = ( 100 * parseFloat(jQuery(this).css('width')) / parseFloat(jQuery(this).parent().css('width')) ) + '%';
    jQuery(this).width('0%').animate({width: w}, 1200, function() { jQuery('.meter-wrapper .progress').fadeIn(); } );
    });

  31. Really great job! And thanks for sharing…

  32. Some guy

    Looks bad in Firefox 3 however.

  33. I really like how this looks and works! Thank you.

This comment thread is closed. If you have important information to share, you can always contact me.