How to make a progress bar in CSS

Warren Davies Avatar

Follow on Twitter

How can you make really cool CSS progress bars? What HTML do you need? And how do you use CSS and JavaScript to make it actually work?

In this post I’ll tell you everything you need to know about progress bars. But in order to do that, first I need to take you on a little journey to Hollywood…

Progress bars hit the big screen

Every once in a while, elements from our line of work show up in pop culture – and in 2008, the humble progress bar got a chance to star in a blockbuster Hollywood movie.

In the excellent Iron Man, genius inventor Tony Stark is kidnapped, trapped in a cave, and forced to build a missile for some nasty terrorists. But instead of doing this, our innovative hero builds a suit of armour loaded up with all kinds of weapons, and uses it to escape.

There’s a really cool scene when he’s just about to break out of his cavey prison. He asks Yinsen – his cellmate and assistant – to load up the software for the suit.

Stark: Initialise the sequence. Now!
Yinsen: Tell me, tell me!
Stark: Function 11. You should see a progress bar. Tell me when you see it.
Yinsen: I have it!

Here’s the scene:

https://www.youtube.com/watch?v=FHU49CD0TOs

I love this. Stark had been captured by ruthless terrorists, and secretly plotting his escape. There was no time to lose – a single wasted second could mean the difference between life and death. But he still took the time to code up a visual progress bar for the suit’s initialisation sequence.

This guy takes UX really seriously!

Now, if Stark can do that in a cave (with a box of scraps) – then you my friend, with the full power of CSS at your disposal, have no excuse!

So let’s talk about a few ways you can create a progress bar in CSS.

The quickest and easiest CSS progress bar

In its simplest form, a progress bar just needs two elements:

  • One element to create a gap or space that will be filled (you might call it the ‘track’ or ‘container’)
  • Another element to actually fill that space (the bar itself)

To be fair to Tony Stark, this simple option is the one he went for when stuck in the cave. It’s pretty easy to set up – in fact, lets’s replicate the one Stark made:

<div class="progress"> 
  <div class="progress__bar"></div>
</div>
Code language: HTML, XML (xml)

So progress is the container, and progress__bar is the element that will fill it up and indicate progress. Now for the CSS:

.progress {
  margin: 50px auto;
  padding: 2px;
  width: 100%;
  max-width: 500px;
  border: 3px solid #05e35e;
  height: 30px;
}

.progress .progress__bar {
  height: 100%;
  width: 0%;
  background-color: #05e35e;
  animation: fill-bar 3s infinite;
}

@keyframes fill-bar {
  from {width: 0%;}
  to {width: 100%;}
}
Code language: CSS (css)

Set whatever height and width you need for progress, and give it a border. You’ll basically just end up with a rectangle. To replicate the bar in the movie, I’ve added 2px of padding, which creates a little gap between the progress bar itself and the area it’s filling up.

For progress__bar, I’ve set the height to 100% and width to 0%. BUT… I’ve added an animation, which increases the width from 0 to 100%. I’ve set that animation to repeat every 3 seconds.

Here’s how it looks:

Pretty nice!

Of course, to show the actual progress of something, we can’t just have an automatic animation. We’d have to match the percentage width of progress__bar to the percentage completion of the thing your user is waiting for – whether that’s files loading, data processing, or whatever. You’ll need JavaScript for this – we’ll come back to it in a bit.

But first, let’s investigate some ways to style these progress bars.

Styling of CSS progress bars

Once you’ve got that basic structure – the container, and the element that fills it – you can got wild and style the bar however you want. It’s completely up to you!

Here’s a simple version:

.progress {
  margin: 50px auto;
  padding: 2px;
  width: 100%;
  max-width: 500px;
  background: white;
  border: 3px solid #000;
  border-radius: 20px;
  height: 30px;
}

.progress .progress__bar {
  height: 100%;
  width: 5%;
  border-radius: 15px;
  background: repeating-linear-gradient(
    135deg,
    #036ffc,
    #036ffc 20px,
    #1163cf 20px,
    #1163cf 40px
  );
  animation: fill-bar 3s infinite;
}
Code language: CSS (css)

All I’ve done here is change the colours, round off the corners, and add a gradient background to the bar. Here’s how it looks now:

That’s quite cool – I guess the rounded corners make it a a bit friendlier than the hard-edged version we had before (as Steve Jobs said, “Rectangles with rounded corners are everywhere! Just look around this room!”. Try it – look around your room).

Oh, by the way, there are a couple of things to look out for if you want to use rounded corners:

  • The bar itself will usually be smaller that the containing element, so you’ll usually want a little less border-radius on the bar than you have on the container
  • Let’s say you use a 15px border-radius like I have here. That border won’t reach its full radius until the bar is 30px wide. If it’s smaller than that, the border radius must be smaller too, so the bar will look taller that you want it to. To get around this, consider giving the bar a little headstart – go from 5% to 100% rather than 0% to 100%. This will ensure the progress bar doesn’t overlap with its container, like this (I’ve frozen it at 3% width so you can see what I mean):

Feel free to copy or fork these Codepens and play around with different colours, styles, and ideas. Let us know if you create something you like!

Semantics – the progress HTML element

HTML5 includes a <progress> element, which is the semantially-correct way to make a progress bar. There are two kinds:

  • Determinate: When you have specified a current value of the bar (e.g., 50%).
  • Indeterminate: When you haven’t specified a current value of the bar.
<!-- Determinate -->
<progress id="determinate"  value="75" min="0" max="100"> 50% </progress>

<!-- Indeterminate -->
<progress id="indeterminate"></progress>
Code language: HTML, XML (xml)

And here’s how they look (you can also assign label elements to them):

The indeterminate one could be handy in some situations – maybe when you’re not sure how long the user has to wait, or before a download actually starts.

Styling the HTML progress element

What’s tricky about progress is that each browser displays them differently by default – usually, but not always, based on the operating system and browser you’re using. And they are a bit fiddly to style.

To customise them, you first you need to remove the browser’s default styles by setting appearance to none:

progress {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}
Code language: CSS (css)

Note that you can target determinate progress elements and indeterminate ones separately:

/* Styles determinate progress bars (with a value property) */
progress[value] {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

/* Styles indeterminate progress bars (without a value property) */
progress:not([value]) {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}
Code language: CSS (css)

Now to add your own styles! Again, it’s a little tricky. In Chrome and Safari, you use the -webkit-progress-bar pseudo class to style the container part of the bar, and -webkit-progress-value to style the actual bar that fills it up:

progress::-webkit-progress-bar {
    background: #;
    box-shadow: 0 2px 3px;
    border-radius: 3px;
}

progress::-webkit-progress-value {
    background-color: #CC0000;
    border-radius: 3px;
}
Code language: CSS (css)

For Firefox, you style the container with the progress element itself, and the bar with -moz-progress-value, like this:

progress {
  background: linear-gradient(#ccc 0%, #eee 50%, #eee 50%, #ccc 100%);
  border-radius: 2px;
  width: 500px;
  margin: 0px auto;
  max-width: 500px;
  height: 30px;
}

progress::-moz-progress-value {
  background: linear-gradient(#b98cf5 0%, #f0e6ff 50%, #f0e6ff 50%, #b98cf5 100%);
    border-radius: 3px;
}
Code language: CSS (css)

Like I said, a bit tricky! Both of those will give you this:

However, note that these pseudoclasses are non-standard – Mozilla doesn’t recommend we use them. But if you’re worried about semantics and accessibility (as you should be!), there’s a compromise to consider…

The progressbar role

There is an aria role you can use on progress bars – which is called, believe it or not, progressbar! This will help make the progress bar usable to people using screen readers and other assistive technologies.

For this, you’d just use a normal div, but add the progressbar role to it. There are aria equivalents to the properties mentioned above:

  • aria-valuenow = value
  • aria-valuemin = min
  • aria-valuemax = max
  • aria-valuetext is a new one, it’s basically an alt text

These values don’t affect the actual appearance of the bar. So make sure that as you move the bar, you update aria-valuenow and aria-valuetext by equivalent amounts.

Your html will look something like this:

<div role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuetext="Download progress: 20%" aria-valuemax="100"></div>
Code language: HTML, XML (xml)

Adding JavaScript to show actual progress

Finally, we just need to update our progress bar in-line with the actual progress of the operation.

This is a lot simpler that it sounds – remember all we have to do is update the width property of our progress__bar element, which we can do easily in JavaScript:

let progressbar = document.getElementById("download");

function updateProgressBar(value) {
  if (value >= 0 && value <= 100) {
    progressbar.style.width = value + "%"; 
  }
}
Code language: JavaScript (javascript)

This function:

  • Accepts a value, which we’ve called value.
  • Checks if value is between 0 and 100 (as we’re setting the width as a percentage, it needs to be in this range).
  • If so, it goes to the element with the id of download (which we stored in the progressbar variable), and sets its width value (note: that you’ll also need to add the download ID to the progress__bar element, so that JS can select it).

To make the progress bar move smoothly, you can give it a CSS transition-duration property:

.progress .progress__bar {
  height: 100%;
  width: 0;
  border-radius: 4px;
  background: linear-gradient(
    to right, red, orange , yellow, green, cyan, blue, violet);
  );
  transition: 0.3s;
}
Code language: CSS (css)

Now you’ve got the code that will move the progress bar, all you need to do is call updateProgressBar in your JavaScript, and pass it a new value.

Exactly how to do this will of course depend on what exactly you’re measuring the progress of. For example, if it’s a file download, you could pass an updated value after each individual file has been downloaded – and the value you pass could be based on the size of the file.

It’ll work like this:

If you type a value into the input box and click Submit, a function called handleInput function will be called. That function will take your input, and pass it to updateProgressBar. The bar will then move to the new point that you specified – and the time it takes to get there will be whatever value you set in the transition property of progress__bar – in this case, 0.3 seconds.

Progress bars that don’t need JavaScript

Note that not all progress bars need to be dynamically updated in this way.

One example is a little sneaky. Have you ever gone to something like a price comparison site, where you put in a query and get one of those “Now checking 6 billion businesses to get you the best price!” screens, and a little progress bar pops up with it?

Well, sometimes, those progress bars are completely fake! Developers often put them in to create a fake delay of 1 or 2 seconds, so that it looks like the server is working really really hard for you.

This is a little deceptive, but the argument is that if people expect something to take a little processing time, but then it appears instantly, they might get suspicious. They might doubt that any processing happened at all – maybe they’ll think the site just recommended the ones that give them the highest referral fee.

So if you have a use-case like this, where you just need a progress bar just for the visual effect, you can use the animated versions we talked about at the beginning of this post.

There are also cases when you do need the progress bar to accurately represent progress, but where JavaScript isn’t needed – for example if the progress is happening across multiple pages of the site. For example, maybe you have a multi-page checkout process, or a job application where first you put in your personal details, then your education, then your work history etc.

In these cases, you can just put a static progress bar up on each separate page, with the width of the bar set to an appropriate amount based on the user’s progress so far.

Now it’s your turn!

your turn to shine meme

Congratulations! You’ve just learned the fundamentals of how to create CSS progress bars for your site. The fact that you’re looking to set up a progress bar suggests that you’re working on a somewhat complex site at the moment. That’s cool!

If you wanna take your site to the next level, take a peek at fullPage.js. You know those cool full-page sites? The ones where you scroll down, and get a nice animation, and then you’re taken to a whole new page? Well fullPage.js is how you can make them!

You get a boatload of different effects and navigation options, and it fits right in with WordPress or your favourite JavaScript framework. Check it out, and see if you like it!

Was this page helpful?