Progress Element in HTML5
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. Usingprogress
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;
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.
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 usingborder: 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.
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.