How to style a progress bar using CSS

Temani Afif

Web Developer ··Website Tips

How to style a progress bar using CSS

HTML offers a native element to show progression which is <progress>. In this post, we will learn a few CSS tricks to style such an element. We will also see how to make it look the same across browsers because the native element will, by default, render differently based on the browser.

First, let’s start by defining the HTML code:

<label>Progression: <progress value="70" max="100">70 %</progress></label>

We consider a <label> where we put the <progress> element and a relevant text that reflects the progression we want to highlight. Then, we define two attributes for our element. The max value which is, in most of the cases, equal to 100 to indicate 100% then the value that indicates the progress and it should be within the range [0 max]

There is no specific requirement or restriction on the value we should use. We can for example consider

<label>Progression: <progress value=".7" max="1">70 %</progress></label>

You may notice the text I am adding inside the progress as well (70%). This one is not mandatory but recommended as a fallback in case we are using an old browser that doesn’t support the <progress> element. In 2022, it’s quite rare to deal with such browsers but it’s always good to have a good coverage even for very old browsers.

Before we deal with CSS it should be noted that the implementation of <progress> element is different for each browser in the sense that its inner HTML structure is different. We mainly have 2 structures.

The one used by Firefox:

<progress>
  <div pseudo="-moz-progress-bar"></div>
</progress>

And the one used by Webkit and blink browsers such as Chrome, Safari, and Opera:

<progress>
 <div pseudo="-webkit-progress-inner-element">
   <div pseudo="-webkit-progress-bar">
     <div pseudo="-webkit-progress-value"></div>
   </div>
 </div>
<progress>

Such a difference is one reason that makes styling the progress element a tedious task as we have to write a style that matches both structures.

How to make a horizontal progress bar


Let’s start with the most basic example:

The first thing to do is to disable the default browser styles by using

progress[value] {
  -webkit-appearance:none;
  -moz-appearance:none;        
  appearance: none;
}

You may wonder why I am adding [value] to the selector. It’s because the progress element without the value attribute will not indicate a progression and we are not interested in such behavior in our article:

If there is no value attribute, the progress bar is indeterminate; this indicates that an activity is ongoing with no indication of how long it is expected to take. ref

Then we will style the element for the Firefox browser using:

progress[value] {
  --color: blue;  /* the progress color */
  --background: lightgrey; /* the background color */
  
  border: none; /* Firefox add a default border */
  width: 200px;
  margin: 0 10px;
  border-radius: 10em;
  background: var(--background);
}
progress[value]::-moz-progress-bar {
  border-radius: 10em;
  background: var(--color);
}

The code should be self-explanatory. The main element will take the gray coloration defined with --background while the progress bar will get the blue color defined with --color. Everything else is basic CSS to give some dimension, rounded corners, and margin.

For the Webkit and blink browsers, we will do the same but with different selectors

progress[value] {
  --color: blue; /* the progress color */
  --background: lightgrey; /* the background color */

  width: 200px;
  margin: 0 10px;
}
progress[value]::-webkit-progress-bar {
  border-radius: 10em;
  background: var(--background);
}
progress[value]::-webkit-progress-value {
  border-radius: 10em;
  background: var(--color);
}

Finally, we put all the styles together and we are done!

progress[value] {
  --color: blue; /* the progress color */
  --background: lightgrey; /* the background color */

  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  width: 200px;
  margin: 0 10px;
  border-radius: 10em;
  background: var(--background);
}
progress[value]::-webkit-progress-bar {
  border-radius: 10em;
  background: var(--background);
}
progress[value]::-webkit-progress-value {
  border-radius: 10em;
  background: var(--color);
}
progress[value]::-moz-progress-bar {
  border-radius: 10em;
  background: var(--color);
}

Now that we know how to style the <progress> element let’s go further and make it look fancier. To do this, I will simply update coloration by adjusting the --color variable.

Let’s do the following:

--color: 
    linear-gradient(#fff8,#fff0),
    repeating-linear-gradient(135deg,#0003 0 10px,#0000 0 20px),
    #31c6f7;

By using a gradient coloration instead of a simple color we get another great looking for our element. You can imagine all the possibilities we can have.

Here is another idea:

For the above, I have introduced a new variable to define the width that I am also using to define the gradient dimension.

We can also have a dynamic coloration where the color change based on the value of the progress:

Thanks to the use of calc() combined with the variable --w we can express a condition based on the value to have a dynamic coloration. I have considered three colors but we can easily extend to more.

Another idea where I will add a mask to get another fancy progress bar:

How to make a circular progress bar


In this section, I am going to create two types of circular progress bars, still, with the same HTML structure used in the previous section.

Here is the result of the first type

To achieve the above, we need to style the <label> to make a square element and place its content at the center.

label {
  --w: 150px; /* the width*/
  /* we use CSS grid to center the content */
  display: inline-grid;
  place-content: center;
  /* */
  width: var(--w);
  aspect-ratio: 1; /* make a square element */
  position: relative;
}

Note that I am also using position: relative because I am going to use position: absolute with the <progress> element.

progress[value] {
  --color:  /* the progress color */
    /* if < 30% "red" */
    linear-gradient(red    0 0) 0 /calc(var(--w)*.3 - 100%) 1px,
    /* if < 60% "orange" */
    linear-gradient(orange 0 0) 0 /calc(var(--w)*.6 - 100%) 1px,
    /* else "green" */
    green;
  --background: lightgrey; /* the background color */

  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  /* (1) */
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  /* */
  border-radius: 50%;
  overflow: hidden;
  background: var(--background);
  transform: rotate(-90deg); /* (2) */
  -webkit-mask: radial-gradient(#0000 59%,#000 60% 70%,#0000 71%) /* (3) */
}
progress[value]::-webkit-progress-bar {
  background: var(--background);
}
progress[value]::-webkit-progress-value {
  background: var(--color);
}
progress[value]::-moz-progress-bar {
  background: var(--color);
}

A big part of the code is similar to what we already did in the first section of the article. For the rest of the code:

  1. We are making our element full width/height of the label
  2. We are adding a rotation to make the progression goes from bottom to top instead of left to right
  3. We are applying a mask to cut the inner part

Below is a figure to better understand the trick:

Step by step to create a circular progress bar

Let’s move to the second type which is the most common one:

For this one, I am going to “cheat” a lit bit by modifying the HTML like the below:

<label> label <progress max="100" value="10" style="--p:10%">10%</progress></label>

I had to introduce an extra CSS variable defined as an inline style that should be equal to the progression. I will use that variable for the gradient configuration that will define the colors:

conic-gradient(#31c6f7 var(--p),lightgrey 0)

Note that for this type of progress we only need one value for the color applied to the main element. We are making the inner elements transparent:

progress[value] {
  --background: conic-gradient(#31c6f7 var(--p),lightgrey 0); 

  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border: none;
  position: absolute;
  inset: 0;
  height: 100%;
  width: 100%;
  border-radius: 50%;
  overflow: hidden;
  background: var(--background);
  -webkit-mask: radial-gradient(#0000 59%,#000 60% 70%,#0000 71%)
}
progress[value]::-webkit-progress-bar {
  background: var(--background);
}
progress[value]::-webkit-progress-value {
  background: #0000; /* transparent */
}
progress[value]::-moz-progress-bar {
  background: #0000; /* transparent */
}

We can also consider another variation of the previous design. We remove the grey coloration and add rounded edges:

For this one, I am defining the variable --p inside the label instead of the progress element because I will need to define a pseudo-element that will create one of the rounded edges. Note that it needs to be without the percentage unit this time:

<label style="--p:10"> label <progress max="100" value="10">10%</progress></label>

I also introduced a CSS variable for the color to easily update it. I also need that variable to make the color of the pseudo-element match the color of the gradient.

I have used the same technique to create a CSS-only Pie Chart so if you want to get more detail about the different values I invite you to read the following article: https://www.freecodecamp.org/news/css-only-pie-chart/

Conclusion


Through different examples of progress components, we saw how to style the native <progress> element using only CSS and how to make it look the same cross-browser. Using the same minimal HTML code we can easily get a different result that we can easily control thanks to CSS variables and some modern CSS techniques.

Frequently Asked Questions

How do I choose a design for my website?

One of the most important things when creating a website for your art is the design. Even though your pieces of art might be amazing, people will leave if your site is hard to navigate. This is why it’s important that the site is easy on the eyes and easy to navigate.

Why is unmanaged hosting cheaper?

Since with unmanaged hosting, there is no routine support – i.e., no management of your website, it is cheaper than managed hosting.

Do I need web developer skills to use Shopify or WooCommerce?

No. Both platforms welcome beginners, but you might get more out of WooCommerce’s extensions if you’ve got some previous experience.

How much does a domain cost monthly?

Usually, domain names cost from $0.99 to $12 per month. However, at Verpex, we offer a free domain name with our hosting packages.

Temani Afif
About the Author
Temani Afif

Temani Afif is an expert web developer, a content creator, and a CSS addict. He is the mastermind behind css-challenges.com, css-generators.com, css-only.art, css-articles.com, and a lot of CSS stuff. He's also an active contributor at Stack Overflow answering all kinds of CSS questions.

View all posts by Temani Afif
Discount

Enter Discount Code MOVEME During The SIGN UP Process To Get 90% Off Your First Month

with the discount code

MOVEME

Use Code Now
Jivo Live Chat