Note:
The stylesheet for the polyfill was not included correctly when this post was first published, and as a result, the examples below were not showing up correctly on IE7-8 and Safari 5.1+. The problem has been corrected.

Screenshots of HTML5 progress bars with different styles applied. Details given below.
<div> tags, CSS and a litle bit of math, but now I like to do it the HTML5 way using the <progress> tag. This article will discuss how this tag is rendered by default in all operating systems and browsers and how to style the progress tag with CSS, even in browsers that don’t officially support the it. It will also discuss some interesting limitations of all the browser implementations amd show some interesting examples using advanced CSS3 techniques.
The HTML: Simple
The HTML for a Progress bar is dead simple:
<progress max="100" value="60"> <strong>Progress: 60% done.</strong> </progress>
Note that the HTML inside the <progress> tag is the fallback for browsers that do not support it. That, unfortunately, includes all versions of IE and Safari so far, as well older versions of Firefox (5.x and lower) and Opera (10.x and lower). Although the fallback is acceptable, we can go a step further and use Lea Verou’s excellent <progress> tag polyfill, which adds pretty much full-support for all of these browsers except for Safari 5 and lower (so you should always put in the fallback HTML just to be on the safe side). Let’s take a look at these screenshots to see what how <progress> looks across the browserverse:
| Windows 7 | Windows XP | Mac OS X | Ubuntu Linux | |
|---|---|---|---|---|
| Firefox | ![]() |
![]() |
![]() |
![]() |
| Chrome | ![]() |
![]() |
![]() |
![]() |
| IE7+ (polyfill) | ![]() |
N/A | ||
| Safari 5.1+ (polyfill) | ![]() |
![]() |
![]() |
N/A |
| Opera | ![]() |
![]() |
![]() |
![]() |
Note that:
- Firefox and Chrome will render the progress bar the same way that the host operating system would … except for Chrome for Linux, which uses it’s own custom style (thanks to Mounir Lamouri for correcting me on this exception).
- The color of the Opera progress value is always green (more on this later).
- The browsers that use the polyfill all render the progress bar with a nice bluish gradient effect
A progress bar can also have an “indeterminate” state, which happens when there is no value attribute.
<progress max="100"> <strong>Progress: 60% done.</strong> </progress>
This effect is used to show that the state of progress is currently unknown (e.g. how long it will take for a web server to initiate the download of a file if is generating it on the fly). How this looks varies from browser to browser as well.
| Windows 7 | Windows XP | Mac OS X | Ubuntu Linux | |
|---|---|---|---|---|
| Firefox | ![]() |
![]() |
![]() |
![]() |
| Chrome | ![]() |
![]() |
![]() |
![]() |
| IE7+ (polyfill) | ![]() |
N/A | ||
| Safari 5.1+ (polyfill) | ![]() |
![]() |
![]() |
N/A |
| Opera | ![]() |
![]() |
![]() |
![]() |
Note that:
- Opera is the only browser that doesn’t distinguish between a progress bar with an indeterminate state and one with a value of zero.
- All of the other browsers (including the ones that use the polyfill) animate the indeterminate states. (I have opted to not show all the animations here to avoid readers getting seizures … I hear that would be a bad thing).
But I Want To Style Them My Way!
If you are particular in how you want your <progress> tags to look, the good news is that you can pretty much style them any way you want. You must, however, be aware about the browser quirks that can trip you up … and it isn’t all IE’s fault this time! Follow this three-to-four-step process, and you’ll be styling progress bars in your sleep in no time:
Step 1: Turn off default styling
The first step is to turn off the default styling in all browsers:
progress, /* All HTML5 progress enabled browsers */
progress[role] /* polyfill */
{
/* Turns off styling - not usually needed, but good to know. */
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
/* gets rid of default border in Firefox and Opera. */
border: none;
/* Needs to be in here for Safari polyfill so background images work as expected. */
background-size: auto;
/* Dimensions */
width: 400px;
height: 60px;
}
/* Polyfill */
progress[role]:after {
background-image: none; /* removes default background from polyfill */
}
/* Ensure fallback text doesn't appear in polyfill */
progress[role] strong {
display: none;
}
Simple stuff: remove the border and add a specific width and height. I added the second rule to remove the background image inserted by the polyfill’s stylesheet, but if you wanted, you can modify the polyfill’s stylesheet directly (or leave it out completely if you elect not to use the polyfill). The final rule is to ensure the polyfill doesn’t display the fallback content — it assumes that is always wrapped in a <strong> tag, so this may be something you should keep in mind when setting the default content (if you don’t like using a <strong> tag as a wrapper for your fallback content, use whatever tag you like).
Note that the appearance property (and its vendor-specific brethren) are there to turn off the default operating-system styling on the progress bar — it doesn’t seem like it is really necessary, but I put it here for reference in case it becomes mandatory in the future.
Step 2: The Progress Bar Background.
Now let’s change the background color of the progress bar to a light red.
progress, /* Firefox */
progress[role][aria-valuenow] { /* Polyfill */
background: #ffeeee !important; /* !important is needed by the polyfill */
}
/* Chrome */
progress::-webkit-progress-bar {
background: #ffeeee;
}
Notice that with Firefox and the polyfilled browsers, all you need to do is change the background of just the progress tag itself, while in Chrome (and I assume future versions of Safari) it is necessary to use the -webkit-progress-bar pseudo-element. Note that even though the code inside these rules are the same, you cannot put all of these selectors in one rule: doing so breaks Firefox and Opera (so much for degrading gracefully).
Step 3: The Progress Bar Value
Now let’s change the color of progress bar value to black. The CSS is a wee bit longer than it really should be:
/* Firefox */
progress::-moz-progress-bar {
background: black;
}
/* Chrome */
progress::-webkit-progress-value {
background: black;
}
/* Polyfill */
progress[aria-valuenow]:before {
background: black;
}
Yes, three rules to rule them all! Again, putting them all together in one selector breaks every browser on the planet (including the polyfilled ones), so we have to write three separate rules with the same CSS properties in them! <sarcasm>Yaaayyy!!!!</sarcasm>
Note that there is no way that I know of to style the progress bar value in Opera 11.52 and lower. It just stays the same green no matter what you do. :-( If anyone is reading this and knows otherwise, I would be indebted to you to let me know how.
Step 4: The Indeterminate Value
This part is optional, and I would only put these rules in if I know I’ll need a style for the indeterminate value (not all applications need it):
/* Firefox */
progress:not([value])::-moz-progress-bar {
background-image: url(../images/indeter.gif);
}
/* Chrome */
progress:not([value])::-webkit-progress-bar {
background-image: url(../images/indeter.gif);
}
/* Polyfill - IE */
progress[role]{
background-image: url(../images/indeter.gif) !important;
}
/* Polyfill - Safari */
progress:not([value]) {
background-image: url(../images/indeter.gif) !important;
background-size: auto; /* Needs to be in here for Safari */
}
It even works in Opera! Note that the background-size must be set to auto in order to override the default style in the polyfill. :-)
See the above CSS in action in a “clean room” page
That’s Too Basic! I Want Fancy-Pants™ Progress Bars
So, now that you know the basics, let’s take a look at some more complicated and interesting progress bars:
Two Image Effect
This progress bar uses two versions of the same image (one grey-scale, one color) to differentiate between the progress bar background and value:
See the above example in action in a “clean room” page
Gradients
Note that not only can you use background-colors and -images, but also use the variety of gradients that are available for developers today in supported browsers (this does not include IE9 and lower). To prove this, I took Chris Croyer beautiful <div> style progress bars from his blog post on CSS3 based progress bars and HTML5-ified™ them here:
Unfortunately, due to CSS gradients being implemented by the browser vendors using vendor-specific prefixes, the CSS tends to be a wee long. Here is the CSS of the first example (scroll the code to see the whole lot of it):
/*
* Gradient Shadow
*/
/* All HTML5 progress enabled browsers */
progress.example3 {
/* Turns off styling - not usually needed, but good to know. */
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
/* gets rid of default border in Firefox and Opera. */
border: solid #cccccc 5px;
border-radius: 10px;
/* Dimensions */
width: 238px;
height: 45px;
}
/* Polyfill */
progress.example3[role]:after {
background-image: none; /* removes default background from polyfill */
}
/*
* Background of the progress bar background
*/
/* Firefox and Polyfill */
progress.example3 {
background: #cccccc !important; /* !important only needed in polyfill */
}
/* Chrome */
progress.example3::-webkit-progress-bar {
background: #cccccc;
}
/*
* Background of the progress bar value
*/
/* Firefox */
progress.example3::-moz-progress-bar {
border-radius: 5px;
background-image: -moz-linear-gradient(
center bottom,
rgb(43,194,83) 37%,
rgb(84,240,84) 69%
);
}
/* Chrome */
progress.example3::-webkit-progress-value {
border-radius: 5px;
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%
);
}
/* Polyfill */
progress.example3[aria-valuenow]:before {
border-radius: 5px;
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%
);
}
Note that I did not use the Gradient filter to polyfill CSS3 gradients in IE6-9. This is because Visual Filters don’t work in CSS3 pseudo-elements like :before or :after. Oh well.
See the above CSS in action in a “clean room” page
Monkeys!
What blog post about HTML5 progress tags would be complete without monkeys? These simians appear in all browser except for IE8 and lower (as explained below).
Apologies to the great Banksy — it’s not his monkey but one grabbed from this Animated GIF site and converted into a CSS sprite using André Gil’s super-cool Gif2TileSet tool. I took this sprite and animated the monkey using blitting whenever the progress bar changes values (thanks to my friend and colleague Noel Tibbles for turning me on to this technique).
Note the HTML has an extra <div> tag so the monkey has a place to live:
<progress class="monkey" min="0" max="100" value="60"></progress> <div class="after"></div>
I wish that I could have used :after (or ::after) rules instead, but these pseudo-elements don’t work with the progress tags in any browser that doesn’t use the polyfill. And no, :before doesn’t work either. I have no idea why it doesn’t work, but it’s a shame — using them would be perfect to get rid of the extra markup.
The CSS that makes the monkey animate when the progress bar changes values is here:
progress.monkey[value="0"] + .after{
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif');
}
progress.monkey[value^="1"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -77px ;
}
progress.monkey[value^="2"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -154px ;
}
progress.monkey[value^="3"] + .after,
progress.monkey[value="100"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -231px !important ;
}
progress.monkey[value^="4"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -308px ;
}
progress.monkey[value^="5"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -385px ;
}
progress.monkey[value^="6"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -462px ;
}
progress.monkey[value^="7"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -539px ;
}
progress.monkey[value^="8"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -616px ;
}
progress.monkey[value^="9"] + .after {
background: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif') 0 -693px ;
}
Each of these rules are applied when the first digit changes in the progress bar, which is done using the ^= attribute rules. This works because the progress bar goes from 0 – 100 and the progress bar increments by 10. They monkeys don’t appear in IE8 and under because of it’s lack of support of the ^= attribute selector (but it works well in IE9).
Note that this would be much easier if CSS allowed us to combine the CSS3 calc() and attr() attributes together like Lea Verou dreams about in one of her blog posts:
/*
* Don't try this - it doesn't work in any browser, but it would be nice if it did.
*/
progress.monkey::after {
background-image: url('/blog/wp-content/uploads/2011/12/monkeyBlit.gif');
background-position-x: 0;
background-position-y: calc(-77 * attr(value), 'px');
}
Hopefully this will come to pass in the future.
See the above CSS in action in a “clean room” page
Speedometer
My last example is radically different than the others. How about we apply the speedometer metephor to the progress bar with some fancy-pants CSS3? This example in all browsers except IE8 and lower (due to lack of support for native CSS3 transform)

The CSS is similar to the monkey example in that I add an extra <div> after the progress tag, except that it has an image of a pointer inside of it.
<div id="progressContainer"> <progress id="rot" class="example_r" min="0" max="100" value="60"></progress> <div data-arrow-for="rot" class="arrow"><img src="/blog/wp-content/uploads/2012/01/hand.png" /></div> </div>
I wrapped both these tags inside a relatively positioned container <div>. This is so I could absolutely position both the progress bar and the arrow in order for them to line up properly. I make the progress bar into a semi-circle by using border-radius and part of a photo from Flickr user ‘listener42′ …
progress.example_r {
/* gets rid of default border in Firefox and Opera. */
border: solid 1px black;
display: inline-block;
/* Produces the semi-circle */
border-radius: 238px 238px 0 0;
/* Dimensions */
width: 238px;
height: 126px;
padding: 0;
}
… and make the arrow rotate as the progress bar increments by using CSS transforms:
progress.example_r[value="0"] + .arrow {
-moz-transform: rotate(270deg);
-webkit-transform: rotate(270deg);
-o-transform: rotate(270deg);
-ms-transform: rotate(270deg);
}
progress.example_r[value^="1"]:not([value="1"]):not([value="100"]) + .arrow {
-moz-transform: rotate(288deg);
-webkit-transform: rotate(288deg);
-o-transform: rotate(288deg);
-ms-transform: rotate(288deg);
}
progress.example_r[value^="2"]:not([value="2"]) + .arrow {
-moz-transform: rotate(306deg);
-webkit-transform: rotate(306deg);
-o-transform: rotate(306deg);
-ms-transform: rotate(306deg);
}
progress.example_r[value^="3"]:not([value="3"]) + .arrow {
-moz-transform: rotate(324deg);
-webkit-transform: rotate(324deg);
-o-transform: rotate(324deg);
-ms-transform: rotate(324deg);
}
progress.example_r[value^="4"]:not([value="4"]) + .arrow {
-moz-transform: rotate(342deg);
-webkit-transform: rotate(342deg);
-o-transform: rotate(342deg);
-ms-transform: rotate(342deg);
}
progress.example_r[value^="5"]:not([value="5"]) + .arrow {
-moz-transform: rotate(360deg);
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
-ms-transform: rotate(360deg);
}
progress.example_r[value^="6"]:not([value="6"]) + .arrow {
-moz-transform: rotate(378deg);
-webkit-transform: rotate(378deg);
-o-transform: rotate(378deg);
-ms-transform: rotate(378deg);
}
progress.example_r[value^="7"]:not([value="7"]) + .arrow {
-moz-transform: rotate(396deg);
-webkit-transform: rotate(396deg);
-o-transform: rotate(396deg);
-ms-transform: rotate(396deg);
}
progress.example_r[value^="8"]:not([value="8"]) + .arrow {
-moz-transform: rotate(414deg);
-webkit-transform: rotate(414deg);
-o-transform: rotate(414deg);
-ms-transform: rotate(414deg);
}
progress.example_r[value^="9"]:not([value="9"]) + .arrow {
-moz-transform: rotate(432deg);
-webkit-transform: rotate(432deg);
-o-transform: rotate(432deg);
-ms-transform: rotate(432deg);
}
progress.example_r[value="100"] + .arrow {
-moz-transform: rotate(450deg);
-webkit-transform: rotate(450deg);
-o-transform: rotate(450deg);
-ms-transform: rotate(450deg);
}
Again, the CSS would be much smaller in size if I could use calc() and attr() together, but again, oh well. Also note that this example doesn’t work in Opera very well because we cannot turn of the green in the progress bar value. :-(
See the above CSS in action in a “clean room” page
Summary of Gotchas
As mentioned before, there are a few annoyances I have found with they way the browsers have implemented HTML5 progress bars:
- You cannot use
:before/::beforeor:after/::afterpseudo-elements on theprogresselement. Why this is not allowed is unclear to me, and I hope this is allowed in the future. - Safari 5.0 and lower cannot use the polyfill, so you should always use the fallback HTML inside the
<progress>tag. - It seems like it is impossible to change Opera’s progress bar style to anything besides green.
- There is a small bug in the polyfill when applying borders to the progress bar. I have a fix that has been submitted as a pull request and I assume being reviewed, but in the meantime, you can get my forked version of the polyfill (with the bug fix) on GitHub.
Help Me Keep This Page Up-To-Date!
If you find out more information about the HTML5 progress tag, I’d love to hear from you! Please let me know in the comments below. I will add relevant information to the above article and credit you fully. :-)


























31 responses so far
3
Cyberdemon
// Jan 4, 2012 at 12:56 am
Mind = blown.
4
sabari
// Jan 4, 2012 at 1:10 am
its amazing and very useful to me.. thank u :)
5
Camilo
// Jan 4, 2012 at 2:07 am
Great article!
It would be nice if select and input files actually have a similar selectors to customize them like that.
6
TheSisb
// Jan 4, 2012 at 4:46 am
This is indeed an excellent, in-depth article.
Thanks for taking the time to explain this so fully!
7
Vladimir
// Jan 4, 2012 at 5:37 am
Great article, thanks! What about IE7-8, and fallback? Some small jQuery wrapper or pure js plugin will be useful (just a bg position is ok for old browsers)
9
Ryan
// Jan 4, 2012 at 9:19 am
Superb :) Filled in a few blanks but like to see it out there!
10
zoltan
// Jan 4, 2012 at 9:50 am
@Vladamir: I believe you are refer to the last example, and yes, it is quite possible to use a js library like my own cssSandpaper to do the animated rotation of the spedometer dial. I just wanted to show how it is now possible to do complex styling of progress elements without any additional JavaScript.
11
Samiullah Khan
// Jan 4, 2012 at 9:54 am
Great Analogy of Html5 progress bars;
The one with gradients are promising!
12
Ed
// Jan 4, 2012 at 10:01 am
Thanks for sharing this information. The summary of gotchas is nice. The second gotcha, about Safari 5.0 seems to have something missing. If I have followed correctly, I think you meant to point out that with Safari 5.0 and lower you should always use the fallback markup. If I haven’t followed correctly, I’ll blame my lack of coffee this morning!
13
NobbZ
// Jan 4, 2012 at 10:30 am
Thank you! And all the past I thought is not stylable because I did not found any informations about styling it… So I sticked to divs until today…
I bookmarked this page and hope it will help me in the future, to rebuild my project!
14
zoltan
// Jan 4, 2012 at 12:04 pm
@Ed: Oops. Typo.. thanks for pointing it out. You are correct… fallback text is good, even when using the polyfill. :)
15
Steve
// Jan 4, 2012 at 12:28 pm
This is really cool! I’m lovin’ HTML5. Thanks for the write up.
16
Marco
// Jan 4, 2012 at 1:56 pm
Awesome stuff Zoltan. I definitely will go back to this when I have a project that needs it. Thank you!!
17
左撇子
// Jan 4, 2012 at 9:11 pm
that is really cool, nice post! Thank you!
18
Gundars
// Jan 5, 2012 at 2:16 pm
Awesome! CTRL+D
19
Diseño de Paginas Web en Tijuana
// Jan 5, 2012 at 2:34 pm
I liked the article would use some of this in future website but with better design! Thanks for sharing.
20
Alex
// Jan 5, 2012 at 2:35 pm
Thanks for the highly useful info! I’m impressed at Opera’s consistency across OS in the default bars – and I love the speedometer example.
21
Subash Aryal
// Jan 6, 2012 at 12:24 am
A good read and easily understood. nice post. Thank you, keep up doing good stuffs like this :)
me @webaryal in Twitter…
22
zoltan
// Jan 6, 2012 at 12:29 am
@Vladimir: Apologies! I was taking a look at this article in IE8 and noticed that the progress bars were not showing up due to the stylesheet not being picked up correctly, which explains your comment. I have fixed this mistake. Thanks for bringing this to my attention. :-)
23
Vladimir
// Jan 6, 2012 at 12:40 am
@zoltan: You welcome!
24
Darto KLoning
// Jan 6, 2012 at 5:01 am
This is so much awesome on the simplicity.
25
mohsen
// Jan 6, 2012 at 9:12 am
thank you ,very useful
26
Jason
// Jan 11, 2012 at 9:48 pm
This looks great – i have been really getting into HTML5 but obviously not enough as i didnt even realise this existed! I would love to implement this as a page / image preloader – can anyone provide a simple example or link to this?
27
zoltan
// Jan 12, 2012 at 9:17 am
@Jason: Out of coincidence, the new article I am working on talks about that. Stay tuned! :)
28
Jason
// Jan 12, 2012 at 5:52 pm
Good stuff Zoltan, you’re site is now in my Xmarks! :)
29
zoltan
// Jan 17, 2012 at 1:01 am
@Jason: Take a look at my new blog post for an image preloader example.
30
Beben Koben
// Jan 25, 2012 at 10:56 pm
experience anymore…hohoho ty
31
Federico
// Feb 1, 2012 at 10:07 am
::before and ::after do not work cause they are create _inside_ the PROGRESS element. And content of this element is replaced with a progress bar. The same goes for the OBJECT element.
Give Feedback
Don't be shy! Give feedback and join the discussion.