CSS Progress Bars

Avatar of Chris Coyier
Chris Coyier on (Updated on )

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

I made some progress bars. They look like this:

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.

UPDATE: It’s been a while. This article was originally written in 2011 and updated in 2015, and now again in 2021. This time I’m just making the demo and Pen and removing a lot of the vendor prefix stuff that isn’t needed anymore. Semantically, you’re probably better off looking at the <progress> and <meter> elements.


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>

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;  <em>/* Can be anything */</em>
  position: relative;
  background: #555;
  border-radius: 25px;
  padding: 10px;
  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%;
  border-top-right-radius: 8px;
  border-bottom-right-radius: 8px;
  border-top-left-radius: 20px;
  border-bottom-left-radius: 20px;
  background-color: rgb(43,194,83);
  background-image: linear-gradient(
    center bottom,
    rgb(43,194,83) 37%,
    rgb(84,240,84) 69%
    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: linear-gradient(to bottom, #f1a165, #f36d0a);

.red > span {
  background-color: #f0a3a3;
  background-image: linear-gradient(to bottom, #f0a3a3, #f42323);


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: linear-gradient(
    rgba(255, 255, 255, .2) 25%, 
    transparent 25%, 
    transparent 50%, 
    rgba(255, 255, 255, .2) 50%, 
    rgba(255, 255, 255, .2) 75%, 
    transparent 75%, 
  z-index: 1;
  background-size: 50px 50px;
  animation: move 2s linear infinite;
  border-top-right-radius: 8px;
  border-bottom-right-radius: 8px;
  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>

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:

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

And call that animation:

.meter > span::after, .animate > span > span {
  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.

@keyframes 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() {
    .data("origWidth", $(this).width())
      width: $(this).data("origWidth") // or + "%" if fluid
    }, 1200);

And done.

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.