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 is1.0
.value
– Indicates the current status of the progress bar. It must be greater than or equal to0.0
and less than or equal to1.0
or the value of themax
attribute (if present).
States of progress bar
A progress bar can be in two states – indeterminate and determinate.
1. Indeterminate
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.
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.
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;
}
On further inspecting the progress element in Chrome Developer Tools, we can see how the spec is implemented.
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. Thebackground-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;
}
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%;
}
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;
Update: Animations don’t seem to work anymore on the pseudo elements within a progress element, in Blink. At the time this was published, they did. Brian LePore reported this to me and pointed me toward this thread discussing it and created a reduced test case. Simuari’s thought:
Maybe it’s the same with the
<progress>
that @keyframes defined outside of the Shadow DOM can’t get accessed by an element inside. From the timing it might have changed in Chromium 39/40?
and also:
what seems to work is moving the animation from
progress::-webkit-progress-bar
toprogress
Bardi Harborow reports that’s not true, though, and also pointed out the logged bug.
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%;
}
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.
Update 11/2016: progress::after { content: attr(value); } does seem to work in Blink now, but nothing else.
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.
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;
}
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 CSS-Tricks (@css-tricks) on CodePen.
The demo should run fine in all 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.
Hey thanks, it’s awesome. Definitively I’m gonna be using this!
Firefox:
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.I think the URL of the article should read –
https://css-tricks.com/html5-progress-element/
instead-of
https://css-tricks.com/html5-element/
Is that a typo?
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?
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.
@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..
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.Pseudo-elements on pseudo-elements. InCSSeption.
Nice work Pankaj. :)
Thanks Hugo!
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!
Nice work, great article.
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.
Was just looking for more info on this yesterday and then found this here today :)
Thanks for the info.. Keep up the good work.
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.Official way… Why do I always make a typo when replying -_-‘
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.
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.
Hi Pankaj,
how comes that you use Pseudo-Elements :before and :after with two : ??
Shouldn’t it be :after instead of ::after?
Bye Kevin
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. ;)
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,
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.
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 :(
Great guide man, adding the loading bar to my new CSS3 site now.
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.
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;
}
Thanks for the comment. You have made couple of valid points, that are worth including in the article.
I don’t know why, but I don’t have to use apperance reset. I’m working on Mac.
I am working on Mac too! but still have to reset the appearance first, when working with
webkit/blink
or-moz
browsers.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 theprogress
element.Hi Patrick – Applying pseudo elements to
-webkit-progress-value
was intentional and not really a mistake :) We now got this query resolved on StackOverflow.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.
What is event like change for it? As I see change event does not work for it.
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!
How did you dig into the
progress
element like that?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.
A few updates on this topic…
Ayrton Fidelis writes in:
And here’s our link post to a Jonathan Snook article talking about animating progress bars.