Grow your CSS skills. Land your dream job.

The HTML5 progress Element

Published by Chris Coyier

The following is a guest post by Pankaj Parashar. Pankaj wrote to me about some pretty cool styled progress elements he created. I asked if he'd be interested in fleshing out the idea into an article about styling them in general. Thankfully, he obliged with this great article about using them in HTML, styling them with CSS as best as you can cross-browser, and fallbacks.

Here is the basic markup for the progress element:

<progress></progress>

As per the standard defined by W3C, the progress element represents the completion progress of a task. A progress element must have both a start tag (i.e. <progress>) and an end tag (i.e. </progress>), even though it looks like a replaced element (like an input). This is good though, as it helps with fallback content as we'll cover later.

Apart from the global attributes, it can have two more attributes:

  • max - Indicates how much task needs to be done before it can be considered as complete. If not specified the default value is 1.0.
  • value - Indicates the current status of the progress bar. It must be greater than or equal to 0.0 and less than or equal to 1.0 or the value of the max attribute (if present).

States of progress bar

A progress bar can be in two states - indeterminate and determinate.

1. Indeterminate


Indeterminate state of the progress bar in Chrome 29 on Mac OS 10.8

Based on your combination of browser and operating system, the progress bar can look different. Zoltan "Du Lac" Hawryluk covers the cross browser behavior of progress element in great depth in his article on HTML5 progress bars (which is definitely worth reading). Wufoo has some screenshots of how it looks on other operating systems on their support page for progress.

It's pretty easy to target and style an indeterminate progress bar because we know that it doesn't contain the value attribute. We can make use of CSS negation clause :not() to style it:

progress:not([value]) {
   /* Styling here */
}

2. Determinate

Throughout this article, we'll only focus on styling the determinate state of the progress bar. So let's change the state by adding the max and value attribute.

<progress max="100" value="80"></progress>

Without applying any CSS, the progress bar looks like this in Chrome 29 on Mac OS 10.8.


Determinate state of the progress bar in Chrome 29 on Mac OS 10.8

Note that only adding the max attribute doesn't change the state of the progress bar because the browser still doesn't know what value to represent.

This is pretty much all that we can do in HTML as rest of the work is done by CSS. At this stage let's not worry about the fallback techniques for supporting older browsers that don't understand the progress element.

Styling progress bars

We can target determinate progress bars using the progress[value] selector. Using progress only is fine as long as you know that you do not have any indeterminate progress bars in your markup. I tend to use the former because it provides clear distinction between the two states. Just like any other element we can add dimensions to our progress bar using width and height:

progress[value] {
  width: 250px;
  height: 20px;
}

This is where the fun part ends and things get complicated because each category of browsers provide separate pseudo classes to style the progress bar. To simplify things, we don't really care about which versions of each browser support the progress element, because our fallback technique will take care of the rest. We classify them as follows:

  • WebKit/Blink Browsers
  • Firefox
  • Internet Explorer

WebKit/Blink (Chrome/Safari/Opera)

Google Chrome, Apple Safari and the latest version of Opera (16+) falls into this category. It is evident from the user agent stylesheet of webkit browsers, that they rely on -webkit-appearance: progress-bar to style the appearance of progress element.


User-agent stylesheet of webkit browsers

To reset the default styles, we simply set -webkit-appearance to none.

progress[value] {
  /* Reset the default appearance */
  -webkit-appearance: none;
   appearance: none;

  width: 250px;
  height: 20px;
}

Progress bar appearance after reset

On further inspecting the progress element in Chrome Developer Tools, we can see how the spec is implemented.


Chrome Developer Tools Snapshot

WebKit/Blink provides two pseudo classes to style the progress element:

  • -webkit-progress-bar is the pseudo class that can be used to style the progress element container. In this demo we'll change the background color, border-radius and then apply inset box shadow to the progress element container.
  • -webkit-progress-value is the pseudo class to style the value inside the progress bar. The background-color of this element by default is green which can be verified by inspecting the user-agent stylesheet. For this demo we will create a candystrip effect using linear-gradient on background-image property.

First we'll style the -webkit-progress-bar (the container):

progress[value]::-webkit-progress-bar {
  background-color: #eee;
  border-radius: 2px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
}

Styling progress bar container

Next we'll style the -webkit-progress-value (the bar) with multiple gradient backgrounds. One for striping, one for top to bottom shadowing, and one for left to right color variation. We'll use the -webkit- prefix for the gradients since we're using it for the progress bar itself anyway.

progress[value]::-webkit-progress-value {
  background-image:
	   -webkit-linear-gradient(-45deg, 
	                           transparent 33%, rgba(0, 0, 0, .1) 33%, 
	                           rgba(0,0, 0, .1) 66%, transparent 66%),
	   -webkit-linear-gradient(top, 
	                           rgba(255, 255, 255, .25), 
	                           rgba(0, 0, 0, .25)),
	   -webkit-linear-gradient(left, #09c, #f44);

    border-radius: 2px; 
    background-size: 35px 20px, 100% 100%, 100% 100%;
}

Styling progress bar value

Adding Animation

At the time of writing only WebKit/Blink browsers support animations on progress element. We'll animate the stripes on -webkit-progress-value by changing the background position.

@-webkit-keyframes animate-stripes {
   100% { background-position: -100px 0px; }
}

@keyframes animate-stripes {
   100% { background-position: -100px 0px; }
}

And use this animation on the -webkit-progress-value selector itself.

-webkit-animation: animate-stripes 5s linear infinite;
        animation: animate-stripes 5s linear infinite;

Pseudo Elements

At the time of writing only WebKit/Blink browsers support pseudo elements ::before and ::after on progress bar. By simply looking at the progress bar, it is not possible to tell the actual value. We can solve this problem by displaying the actual value right at the tail-end of the progress bar using either ::before or ::after.

progress[value]::-webkit-progress-value::before {
  content: '80%';
  position: absolute;
  right: 0;
  top: -125%;
}

Pseudo elements in action

Interestingly, content: attr(value) doesn't work on progress bars. However, if you explicitly specify the text inside the content attribute, it works! I haven't been able to find out the reason behind this behavior. Since this works only on WebKit/Blink browsers, there is no good reason to embed content inside pseudo elements, at least for now.

Similarly, ::after is used to create nice little hinge effect at the end of the progress bar. These techniques are experimental and not really recommended to be used if you are aiming for cross-browser consistency.

progress[value]::-webkit-progress-value::after {
  content: '';
  width: 6px;
  height: 6px;
  position: absolute;
  border-radius: 100%;
  right: 7px;
  top: 7px;
  background-color: white;
}

2. Firefox

Similar to WebKit/Blink, Firefox also uses -moz-appearence: progressbar to paint the progress element.


Firebug screenshot

By using appearence: none we can get rid of the default bevel and emboss. This unfortunately leaves behind a faint border in Firefox which can be removed by using border: none. This also solves the border issue with Opera 12.

progress[value] {
  /* Reset the default appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
  
  /* Get rid of default border in Firefox. */
  border: none;
  
  /* Dimensions */
  width: 250px;
  height: 20px;
}

Faint border in Firefox and Opera

Firefox provides a single pseudo class (-moz-progress-bar) we can use to target the progress bar value. This means that we cannot style the background of the container in Firefox.

progress[value]::-moz-progress-bar { 
  background-image:
    -moz-linear-gradient(
      135deg, 
      transparent 33%, 
      rgba(0, 0, 0, 0.1) 33%, 
      rgba(0, 0, 0, 0.1) 66%, 
      transparent 66% 
    ),
    -moz-linear-gradient(
      top, 
      rgba(255, 255, 255, 0.25), 
      rgba(0, 0, 0, 0.25)
    ),
    -moz-linear-gradient(
      left, 
      #09c, 
      #f44
    );

  border-radius: 2px; 
  background-size: 35px 20px, 100% 100%, 100% 100%; 
}

Firefox doesn't support ::before or ::after pseudo classes on progress bar, nor does it allow CSS3 keyframe animation on progress bar, which gives us a slightly reduced experience.

3. Internet Explorer

Only IE 10+ natively supports progress bar, and only partially. It only allows changing the color of the progress bar value. IE implements value of the progress bar as the color attribute rather than the background-color.

progress[value]  {
  /* Reset the default appearance */
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;

  /* Get rid of default border in Firefox. */
  border: none;

  /* Dimensions */
  width: 250px;
  height: 20px;

  /* For IE10 */
  color: blue; 
}

What about browsers that don't support them?

The progress element is natively supported in: Firefox 16+, Opera 11+, Chrome, Safari 6+. IE10+ is partially supports them. If you want to support older browsers, you've got two options.

1. Lea Verou's HTML5 progress polyfill

Lea Verou's excellent polyfill adds almost full support for Firefox 3.5-5, Opera 10.5-10.63, IE9-10. This also adds partial support in IE8. It involves including progress-polyfill.js file in your HTML and adding CSS selectors that the script file uses. To know more about its usage, check out the CSS source code of the project page.

2. HTML fallback

This is my preferred (no-js) approach. It makes use of a common technique that is also used by audio and video elements.

<progress max="100" value="80">
    <div class="progress-bar">
        <span style="width: 80%;">Progress: 80%</span>
    </div>
</progress>

Simulate the look and feel of progress bar using div and span inside the progress tag. Modern browsers will ignore the content inside the progress tag. Older browsers that cannot identify progress element will ignore the tag and render the markup inside it.

.progress-bar {
  background-color: whiteSmoke;
  border-radius: 2px;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25) inset;

  width: 250px;
  height: 20px;
  
  position: relative;
  display: block;
}
  
.progress-bar > span {
  background-color: blue;
  border-radius: 2px;

  display: block;
  text-indent: -9999px;
}

It is fairly common to use both the techniques in conjunction and it is perfectly safe for production sites. Once you get hold of styling a single progress bar, then adding styles for multiple progress bars is merely an exercise which can be accomplished using classes.

See the Pen Skillset using HTML5 progress bars with CSS3 animations by Pankaj Parashar (@pankajparashar) on CodePen

The demo should run fine for all the browsers including Internet Explorer (down to IE 8). The progress bar color is blue in all the versions of Internet Explorer. Opera 11 and 12 doesn't permit changing the progress bar color. Hence, it shows the default green color. The demo uses additional markup to display some meta information about the progress bar and the percentage value.

For additional reading, check out the HTML5 Doctor article. It covers some similar ground but has some bits about a few additional attributes as well as how to update the bar with JavaScript if you need that.

Comments

  1. rentless
    Permalink to comment#

    Hey thanks, it’s awesome. Definitively I’m gonna be using this!

  2. Permalink to comment#

    Firefox:

    progress[value]
    {
    background-color: #eee;
    border-radius: 5px;
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25) inset;
    position: relative;
    }

    Why have webkit:
    ::-webkit-progress-bar
    ::-webkit-progress-value
    ?

    • That’s a great point! But unfortunately the progress element is not directly hooked to the the progress[value] selector for Webkit/Blink browsers. Hence, the above code will not work in those browsers.

      You can dig deep into the implementation by inspecting the progress element in Developer Tools and learn more about the implementation of the spec.

  3. I think the URL of the article should read –

    http://css-tricks.com/html5-progress-element/

    instead-of

    http://css-tricks.com/html5-element/

    Is that a typo?

  4. Permalink to comment#

    There’s a bug in Safari 5 (and Mobile Safari in iOS 5) where the PROGRESS element is actually destroyed without a trace. This makes it impossible to poly-fill, unless you put your fallback elements outside of the PROGRESS element.

    This is mainly a problem for Windows Safari users (who are stuck on 5). The majority of OS X and iOS users will have moved on to later version.

    • Thanks for pointing out that.

      I knew about this problem with Safari 5, but didn’t knew that it completely destroys the <progress> element. I thought that the fallback markup would work for any case.

      Would you mind sharing a screenshot of this anomaly?

    • MaxArt
      Permalink to comment#

      That’s unfortunate.
      But what’s the adoption rate of Safari on Windows? I think it’s less than IE7, maybe even IE6.

      Most of the old users have switched to Chrome (or other) since then.

    • This link – http://www.w3schools.com/browsers/browsers_safari.asp shows usage statistics of different versions of Safari.

      Safari 5 was .8% for July across all the platforms. For Windows, it should be even less than IE7/6.

    • Kuldeep
      Permalink to comment#

      @Ron, I think Safari 5 (5.1.7), the last for Windows till now, has many native issues regarding performance. It has been haunting my life for sometime now. Most CSS3 transitions, transforms are buggy and flicker, resulting in Safari to crash and restart. Most transitions via jquery also seem to follow up the same sequence. I think this will only lead to the end or complete ignorance of Safari Windows by the Web community, same way as it happened to Opera. I hope Safari 6 on iOS does not have this issue..

  5. afineday
    Permalink to comment#

    Thanks you for this complete post Pankaj ! The styling part will be very usefull for my project.

    I had the same issue with the content: "attr(data-value); in the past and it was a little bit annoying.

  6. Pseudo-elements on pseudo-elements. InCSSeption.

    Nice work Pankaj. :)

  7. Really really good stuff here! I was actually not aware of any of the HTML5 progress code, but I will definitely be using it in my next site- Thanks!

  8. Chris A
    Permalink to comment#

    Nice work, great article.

  9. Permalink to comment#

    “The demo should run fine for all the browsers including Internet Explorer (up to IE 8).”

    Does that mean those pesky remaining IE 6 users can see them in all their glory? :D Think it should read “down to IE 8″.
    But great techniques. The no-js fallback is nice and simple too. Love it.

    • Aah you got me! But more importantly you got the point. :-) I probably started testing from IE11 and managed to successfully test all the versions upto IE8! But you are right, it should have read down to IE8.

    • I fixed that, thanks! I’m gonna bury this since it’s no longer relevant.

  10. Was just looking for more info on this yesterday and then found this here today :)
    Thanks for the info.. Keep up the good work.

  11. Sik
    Permalink to comment#

    The problem is that currently there isn’t any proper way to style progress bars with CSS, which is why each browser does a completely different thing (and probably why Webkit does that weird stuff to style them).

    Until there’s an official bar to style progress bars with CSS, we’re either stuck with all this crazy stuff or resorting to other HTML elements and ignoring <progress> altogether.

    • Sik
      Permalink to comment#

      Official way… Why do I always make a typo when replying -_-‘

  12. Everything is fine, except that proprietary properties which still remind me so called “open web platform” isn’t that open yet or at least doesn’t have common and consistent “foundation” in some places. Not to mention so called “features at risk” (you use one day with fear they may disappear next month from specs) and, putting it mildly, “distributed” docs. That’s why I preferred Flash for many years. Consistency!

    Anyway, very informative and useful article. Good work, Pankaj.

  13. Appreciate your thoughts Wojciech. I can imagine the pain endured by developers in styling the progress bar and this article is an attempt to reduce that.

    However, the situation cannot improve untill there is some cross-browser consistency in the implementation of HTML5 progress element spec for different browser engines.

  14. Kevin
    Permalink to comment#

    Hi Pankaj,

    how comes that you use Pseudo-Elements :before and :after with two : ??

    Shouldn’t it be :after instead of ::after?

    Bye Kevin

    • Permalink to comment#

      In fact, pseudo-elements should be written ::after and ::before to not be confused with pseudo-selector like :hover, :active, etc…

      I think I read it here in an older post. ;)

    • Permalink to comment#

      Kevin –

      Short Answer – There’s no difference between :before and ::before, or between :after and ::after.

      Long Answer – Read this spec defined by W3C on pseudo elements which states that,

      This :: notation is introduced by the current document in order to establish a discrimination between pseudo-classes and pseudo-elements. For compatibility with existing style sheets, user agents must also accept the previous one-colon notation for pseudo-elements introduced in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and :after). This compatibility is not allowed for the new pseudo-elements introduced in CSS level 3.

  15. It’s really really outstanding feature of HTML5. i love it. I have implemented it in my website. it’s really working superb. Thanks for sharing such a great stuff.

  16. Permalink to comment#

    While the article passively mentions this, after reading this and implementing this in a project the other day I have to say that I’d advise against using the progress element right now if you want any sort of “fancy effects” on the main bar.

    Why?

    In short: IE10. The fact that you can only color the bar via the color property makes the bar look very flat and boring compared to Chrome and Firefox, which is really sad considering IE10 supports CSS gradients and animations. Even in IE8/IE9 (via the fallback) you’ll have a nice gradient fill with the filter property. You’ll save a lot of additional CSS and have better visual support if you use plain old DIVs and SPANs.

    That said, I really wanted this to work out… but IE10 is such a downer :(

  17. Permalink to comment#

    Great guide man, adding the loading bar to my new CSS3 site now.

  18. Permalink to comment#

    This is really cool.

    Much easier to use a native solution than have to create one yourself with divs. It’s a shame we will probably need to wait until IE11 for full support.

  19. Two things about Firefox/Gecko implementation:
    – you can actually style indeterminate progress bar with ::indeterminate pseudo-class:
    progress::indeterminate {
    /* */
    }
    – you can style the progress bar background by simply styling the progress element, for example:
    progress {
    background-color: blue;
    }
    progress::-moz-progress-bar {
    background-color: red;
    }

  20. Permalink to comment#

    I don’t know why, but I don’t have to use apperance reset. I’m working on Mac.

  21. content: attr(value) works fine for me in Chrome.

    I made the same mistake you did at first, applying it to the-webkit-progress-value element instead of the progress element.

  22. Hi Patrick – Applying pseudo elements to -webkit-progress-value was intentional and not really a mistake :) We now got this query resolved on StackOverflow.

  23. Permalink to comment#

    Great article !

    Just a small detail : <progress><div></div></progress> doesn’t validate.
    It would be better to use “phrasing content” element instead (like <span>)

    • aah! That’s a nice catch. Although, W3C validation isn’t really a deciding factor, but you’re suggestion is correct.

  24. Permalink to comment#

    What is event like change for it? As I see change event does not work for it.

  25. Filippo
    Permalink to comment#

    Hey I’m from Italy, thats awesome, thanks! I have only a question, if I want to fix the color extremity not to the bar but to the container, so the bar can change color in function of percentage, how can I do? thanks!

  26. A
    Permalink to comment#

    On further inspecting the progress element in Chrome Developer Tools, we can see how the spec is implemented.

    How did you dig into the progress element like that?

    • boomshanka

      I was wondering about the same thing – is it a setting you need to enable and is it available in other browsers as well? I would love to be able to see the markup before the DOM alters it.

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

*May or may not contain any actual "CSS" or "Tricks".