Circle progress bar with pure CSS and HTML

Published: · Reading time: 9 min

Progress bars are widely used on websites to highlight specific data in a more appealing way for users. One benefit of using a circle progress bar over a regular (horizontal) progress bar is that you can fit more progress bars in one row, thus displaying more data to the user.

I’ve already covered how you can style a progress bar in my earlier article. But now we’re going to look at how you can create a circle progress bar.

  1. Setup
  2. Conic gradient
  3. Showing the percentage
  4. Animation
  5. Conclusion

Setup

To display a circle progress bar with CSS we want to use as few HTML elements as possible. A proper way to show a progress bar is to use a semantic HTML element called progress.

<progress value="75" min="0" max="100">75%</progress>

Result:

75%

However, it has specific default styles which make its unique appearance, thus preventing us from using all available CSS properties and displaying it as a circle.

As an alternative, we can use a div element with a progressbar role.

So to create a circle progress bar with only CSS we will need to use a div element anyway. There are two options for the markup.

Option 1

The first option is to use a regular div with a progressbar role along with additional ARIA attributes such as aria-valuenow, aria-valuemin, and aria-valuemax to indicate the range of the progress bar. HTML looks as follows:

<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>

Option 2

The second option is to wrap a progress element inside a div and hide it with CSS. That way we use a native progress element that will still be accessible in the DOM (just visually hidden) and the div will act as a visual representation. HTML looks as follows:

<div class="progress-bar">
  <progress value="75" min="0" max="100" style="visibility:hidden;height:0;width:0;">75%</progress>
</div>

Conic gradient

Since in both HTML approaches we will be styling a div element to create a circular progress bar, we’re going to use gradients. For the background property, we’ll set a radial-gradient function and a conic-gradient function that is available in CSS.

The conic-gradient will show the actual progress (the current value) and the radial-gradient will act as a mask on top of the conic-gradient.

.progress-bar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: 
    radial-gradient(closest-side, white 79%, transparent 80% 100%),
    conic-gradient(hotpink 75%, pink 0);    
}

Result:

To break it down. The conic-gradient function accepts multiple parameters (steps). In our case it is two steps, the first one will start at 0 degrees (12 o’clock) and span all the way up to 75% of the circumference of the circle (9 o’clock).

The radial-gradient in our case represents the mask, and consists of a white color (to match the background) and transparent color to show the progress bar.

The first parameter of the radial-gradient function is the size of the gradient’s ending shape, in our case, it is equal to the closest-side. Meaning the gradient’s ending shape meets the side of the box closest to its center.

After the size of radial-gradient, we specify the color and size of our mask (white overlay) white 79% and transparent color to take up the remaining size of the circle transparent 80% 100%. The one percent difference (79% and 80%) is made to smooth out the border of the gradient.

Browser compatibility for conic-gradient function:

Conic gradient browser support

Browser compatibility for radial-gradient function:

Radial gradient browser support

Showing the percentage

Additionally, let’s display a percentage value in the middle of the circle to inform users about the exact value of our progress bar.

To do so, we can use a pseudo-element with a content property. As for the progress bar div, we’ll need to set additional properties to center the percentage, flex in this case.

.progress-bar {
  display: flex;
  justify-content: center;
  align-items: center;

  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: 
    radial-gradient(closest-side, white 79%, transparent 80% 100%),
    conic-gradient(hotpink 75%, pink 0);    
}

.progress-bar::before {
  content: "75%";
}

Result:

Animation

💡 NOTE: The animation of such a progress bar will only work in Chromium-based browsers.

To make the progress bar animate, we’ll need to specify the value inside a CSS variable, and then change its value inside the animation.

We must use @property rule to use the variable value inside the animation, but like I said previously, this way we are limited to Chromium based browsers.

Specifying CSS variables in a traditional way like --progress-value: 0 won’t work inside the animation, as per specification they are not animatable.

Browser compatibility for @property rule:

@property browser support

We’ll need to change the percentage value to a calc() function to calculate the percentage for the conic-gradient.

Finally, let’s animate the percentage value along with the progress bar. We will do it in a similar fashion. We’ll use existing CSS variable and set it equal to a pseudo-element counter property, as well as specify the animation property.

@property --progress-value {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}

@keyframes progress {
 to { --progress-value: 75; }
}

.progress-bar {
  display: flex;
  justify-content: center;
  align-items: center;

  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: 
    radial-gradient(closest-side, white 79%, transparent 80% 100%),
    conic-gradient(hotpink calc(var(--progress-value) * 1%), pink 0);
  animation: progress 2s 1 forwards;
}

.progress-bar::before {
  counter-reset: percentage var(--progress-value);
  content: counter(percentage) '%';
  animation: progress 2s 1 forwards;
}

Result (refresh page):

Conclusion

While pure CSS solution for circle progress bar is easy to implement it lacks additional customization. For the most part you can only control the width and color of the bar.

Another aspect of this solution, is that it’s not yet fully supported by all browsers, the animation to be exact. So if you’re considering a bulletproof way to display a circle progress bar with animation and possible customizations, you’d might want to use SVG solution.

Finally the end result with complete code can be viewed on CodePen:

See the Pen Untitled by Nikita Hlopov (@nikitahl) on CodePen.

Like this article? Share it on:
Tags: