0 added
0 removed
Original
2026-02-18
Modified
2026-03-09
1
<ul><li><a>Home</a></li>
1
<ul><li><a>Home</a></li>
2
<li><a>Blog</a></li>
2
<li><a>Blog</a></li>
3
</ul><p>Jan 12, 2026</p>
3
</ul><p>Jan 12, 2026</p>
4
<p>Back in the day, building a circular progress bar meant suffering with SVG. Even now, opening DevTools on some of those old implementations still hurts. Today, things have become dramatically simpler.</p>
4
<p>Back in the day, building a circular progress bar meant suffering with SVG. Even now, opening DevTools on some of those old implementations still hurts. Today, things have become dramatically simpler.</p>
5
<p>Modern CSS lets us build a complex, flexible, and visually pleasing circular progress bar using literally a single div and a single CSS property. And all of that comes with excellent browser support.</p>
5
<p>Modern CSS lets us build a complex, flexible, and visually pleasing circular progress bar using literally a single div and a single CSS property. And all of that comes with excellent browser support.</p>
6
<p>In this article, I’ll walk through this exact technique. We’ll start with the core idea, then move on to customization options. After that, we’ll add a bit of visual “polish”, convenient control knobs, and some experimental CSS logic. Yes - at the very end, we’ll even do a bit of programming in CSS.</p>
6
<p>In this article, I’ll walk through this exact technique. We’ll start with the core idea, then move on to customization options. After that, we’ll add a bit of visual “polish”, convenient control knobs, and some experimental CSS logic. Yes - at the very end, we’ll even do a bit of programming in CSS.</p>
7
<h2>The core idea and a basic implementation</h2>
7
<h2>The core idea and a basic implementation</h2>
8
<p>Let’s start with the most important part. The entire implementation revolves around a single element:</p>
8
<p>Let’s start with the most important part. The entire implementation revolves around a single element:</p>
9
<div class="progress"></div><p>And a single property - background-image with multiple background layers. The core idea is simple:</p>
9
<div class="progress"></div><p>And a single property - background-image with multiple background layers. The core idea is simple:</p>
10
<ul><li><p>the top layer is a <strong>radial gradient</strong>that defines the shape of the scale;</p>
10
<ul><li><p>the top layer is a <strong>radial gradient</strong>that defines the shape of the scale;</p>
11
</li>
11
</li>
12
<li><p>the bottom layer is a <strong>conic gradient</strong>that controls the fill.</p>
12
<li><p>the bottom layer is a <strong>conic gradient</strong>that controls the fill.</p>
13
</li>
13
</li>
14
</ul><p>Here’s the basic CSS:</p>
14
</ul><p>Here’s the basic CSS:</p>
15
.progress { width: 150px; height: 150px; background-image: radial-gradient( circle, #222 0% 27%, transparent 28% 50%, #222 51% ), conic-gradient( #D64E42 0% 60%, transparent 60% ); }<p>The radial gradient on top creates the “donut” - a scale with a transparent cut-out center. The conic gradient underneath fills a sector of the circle, effectively controlling the progress.</p>
15
.progress { width: 150px; height: 150px; background-image: radial-gradient( circle, #222 0% 27%, transparent 28% 50%, #222 51% ), conic-gradient( #D64E42 0% 60%, transparent 60% ); }<p>The radial gradient on top creates the “donut” - a scale with a transparent cut-out center. The conic gradient underneath fills a sector of the circle, effectively controlling the progress.</p>
16
<p>It’s worth noting that this approach relies on old - one might even say “ancient” - CSS features: multiple backgrounds, radial and conic gradients, and hard color transitions.</p>
16
<p>It’s worth noting that this approach relies on old - one might even say “ancient” - CSS features: multiple backgrounds, radial and conic gradients, and hard color transitions.</p>
17
<p>Basic circular progress bar implementation</p>
17
<p>Basic circular progress bar implementation</p>
18
<p>This is the key technique that forms the foundation of the progress bar. Everything that follows is just customization and layering on top of it.</p>
18
<p>This is the key technique that forms the foundation of the progress bar. Everything that follows is just customization and layering on top of it.</p>
19
<h2>Customization: scale thickness and transparency</h2>
19
<h2>Customization: scale thickness and transparency</h2>
20
<p>One of the nicest things about this approach is how easy it is to customize.</p>
20
<p>One of the nicest things about this approach is how easy it is to customize.</p>
21
<p><strong>Scale thickness</strong>is controlled by the parameters of the top, radial gradient. We simply tweak the color stop positions:</p>
21
<p><strong>Scale thickness</strong>is controlled by the parameters of the top, radial gradient. We simply tweak the color stop positions:</p>
22
radial-gradient( circle, #222 0% 30%, transparent 31% 54%, #222 55% )<p><strong>Color and transparency of both the scale and the fill</strong>are controlled by the bottom, conic gradient. Change the first color stop to a semi-transparent color - and the fill becomes translucent. Change the second color stop - and the empty part of the scale changes.</p>
22
radial-gradient( circle, #222 0% 30%, transparent 31% 54%, #222 55% )<p><strong>Color and transparency of both the scale and the fill</strong>are controlled by the bottom, conic gradient. Change the first color stop to a semi-transparent color - and the fill becomes translucent. Change the second color stop - and the empty part of the scale changes.</p>
23
conic-gradient( rgba(214, 78, 66, 0.6) 0% 60%, transparent 60% )<p>No extra elements, no masks, no calculations. Just gradient parameters.</p>
23
conic-gradient( rgba(214, 78, 66, 0.6) 0% 60%, transparent 60% )<p>No extra elements, no masks, no calculations. Just gradient parameters.</p>
24
<p>Adjusting scale thickness and fill transparency</p>
24
<p>Adjusting scale thickness and fill transparency</p>
25
<h2>Making it look good: texture, borders, and shadows</h2>
25
<h2>Making it look good: texture, borders, and shadows</h2>
26
<p>The basic version works, but it looks a bit rough. Let’s add some visual “expensiveness”.</p>
26
<p>The basic version works, but it looks a bit rough. Let’s add some visual “expensiveness”.</p>
27
<p>First, we add a third background layer - a repeating conic gradient that creates subtle radial sections:</p>
27
<p>First, we add a third background layer - a repeating conic gradient that creates subtle radial sections:</p>
28
repeating-conic-gradient( rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.2) 4.5%, transparent 5% )<p>Second, we make the top radial gradient more complex by adding intermediate colors to create thin borders:</p>
28
repeating-conic-gradient( rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.2) 4.5%, transparent 5% )<p>Second, we make the top radial gradient more complex by adding intermediate colors to create thin borders:</p>
29
radial-gradient( circle, #222 0% 27%, #333, transparent 28% 50%, #333, #222 51% )<p>And finally, we slightly round the progress bar itself and add shadows:</p>
29
radial-gradient( circle, #222 0% 27%, #333, transparent 28% 50%, #333, #222 51% )<p>And finally, we slightly round the progress bar itself and add shadows:</p>
30
.progress { border-radius: 10px; box-shadow: inset 0 0 1px #666, 0 0 30px black; }<p>None of this affects the logic of the component - it’s purely visual.</p>
30
.progress { border-radius: 10px; box-shadow: inset 0 0 1px #666, 0 0 30px black; }<p>None of this affects the logic of the component - it’s purely visual.</p>
31
<p>Final styling</p>
31
<p>Final styling</p>
32
<h2>Control knobs: CSS variables</h2>
32
<h2>Control knobs: CSS variables</h2>
33
<p>Now let’s make the progress bar practical for real interfaces. To do that, we introduce a CSS variable called --progress.</p>
33
<p>Now let’s make the progress bar practical for real interfaces. To do that, we introduce a CSS variable called --progress.</p>
34
.progress { --progress: 45; --corner: calc(1% * var(--progress, 0)); background-image: radial-gradient( circle, #222 0% 27%, #333, transparent 28% 50%, #333, #222 51% ), conic-gradient( #D64E42 0% var(--corner), transparent var(--corner) ); }<p>Now progress is expressed as a clear number from 0 to 100, and CSS automatically converts it into the required angle.</p>
34
.progress { --progress: 45; --corner: calc(1% * var(--progress, 0)); background-image: radial-gradient( circle, #222 0% 27%, #333, transparent 28% 50%, #333, #222 51% ), conic-gradient( #D64E42 0% var(--corner), transparent var(--corner) ); }<p>Now progress is expressed as a clear number from 0 to 100, and CSS automatically converts it into the required angle.</p>
35
<p>It’s easy to create multiple instances of the component:</p>
35
<p>It’s easy to create multiple instances of the component:</p>
36
<div class="progress" style="--progress: 15"></div> <div class="progress" style="--progress: 50"></div> <div class="progress" style="--progress: 90"></div><p>Controlling progress with a CSS variable</p>
36
<div class="progress" style="--progress: 15"></div> <div class="progress" style="--progress: 50"></div> <div class="progress" style="--progress: 90"></div><p>Controlling progress with a CSS variable</p>
37
<h2>Experimental CSS features: conditional logic</h2>
37
<h2>Experimental CSS features: conditional logic</h2>
38
<p>Now let’s take a small peek into the future of CSS. In modern versions of Chrome, you can already use conditional logic via if().</p>
38
<p>Now let’s take a small peek into the future of CSS. In modern versions of Chrome, you can already use conditional logic via if().</p>
39
<p>For example, you can change the scale color depending on the progress value:</p>
39
<p>For example, you can change the scale color depending on the progress value:</p>
40
.progress { --color: if( style(--progress <= 33): #D64E42; style(--progress <= 66): #F5B848; style(--progress <= 99): #58B473; else: white; ); }<p>And then use that color in the gradient:</p>
40
.progress { --color: if( style(--progress <= 33): #D64E42; style(--progress <= 66): #F5B848; style(--progress <= 99): #58B473; else: white; ); }<p>And then use that color in the gradient:</p>
41
conic-gradient( var(--color) 0% var(--corner), transparent var(--corner) )<p>You can also add a label in the center of the progress bar that changes depending on the progress value. We add a pseudo-element and use if() in the content property to bind the label text to progress ranges. For the label color, we reuse the parent’s --color variable.</p>
41
conic-gradient( var(--color) 0% var(--corner), transparent var(--corner) )<p>You can also add a label in the center of the progress bar that changes depending on the progress value. We add a pseudo-element and use if() in the content property to bind the label text to progress ranges. For the label color, we reuse the parent’s --color variable.</p>
42
.progress { display: flex; justify-content: center; align-items: center; } .progress::before { content: if( style(--progress <= 33): "low"; style(--progress <= 66): "med"; style(--progress <= 99): "high"; else: "go!"; ); color: var(--color); }<p>Important: these are experimental features. If you need solid browser support, use JavaScript together with modifier classes instead.</p>
42
.progress { display: flex; justify-content: center; align-items: center; } .progress::before { content: if( style(--progress <= 33): "low"; style(--progress <= 66): "med"; style(--progress <= 99): "high"; else: "go!"; ); color: var(--color); }<p>Important: these are experimental features. If you need solid browser support, use JavaScript together with modifier classes instead.</p>
43
<p>Conditional styling based on progress</p>
43
<p>Conditional styling based on progress</p>
44
<h2>Wow factor: native animation</h2>
44
<h2>Wow factor: native animation</h2>
45
<p>And finally - the fun part. This progress bar works beautifully with native CSS animations.</p>
45
<p>And finally - the fun part. This progress bar works beautifully with native CSS animations.</p>
46
<p>First, we register the --progress variable:</p>
46
<p>First, we register the --progress variable:</p>
47
@property --progress { inherits: true; initial-value: 0; syntax: "<number>"; }<p>And then animate it:</p>
47
@property --progress { inherits: true; initial-value: 0; syntax: "<number>"; }<p>And then animate it:</p>
48
.progress { animation: progress 10s infinite alternate; } @keyframes progress { to { --progress: 100; } }<p>During the animation, the following update automatically:</p>
48
.progress { animation: progress 10s infinite alternate; } @keyframes progress { to { --progress: 100; } }<p>During the animation, the following update automatically:</p>
49
<ul><li><p>the fill size,</p>
49
<ul><li><p>the fill size,</p>
50
</li>
50
</li>
51
<li><p>the scale color,</p>
51
<li><p>the scale color,</p>
52
</li>
52
</li>
53
<li><p>the center label.</p>
53
<li><p>the center label.</p>
54
</li>
54
</li>
55
</ul><p>In other words, all styles tied to --progress are recalculated on the fly as the native animation updates the variable.</p>
55
</ul><p>In other words, all styles tied to --progress are recalculated on the fly as the native animation updates the variable.</p>
56
<p>Progress bar animation</p>
56
<p>Progress bar animation</p>
57
<h2>Conclusion</h2>
57
<h2>Conclusion</h2>
58
<p>The key technique throughout this article is using multiple backgrounds with radial and conic gradients. It’s simple, well-supported, and extremely flexible.</p>
58
<p>The key technique throughout this article is using multiple backgrounds with radial and conic gradients. It’s simple, well-supported, and extremely flexible.</p>
59
<p>Everything else - customization, visual effects, conditional logic, animation - is just an optional layer on top.</p>
59
<p>Everything else - customization, visual effects, conditional logic, animation - is just an optional layer on top.</p>
60
<p>If you need a reliable production-ready component, stick to the basic technique. If you’re in an experimental mood and browser support allows it, try the newer features - they’re already available in recent versions of Chrome.</p>
60
<p>If you need a reliable production-ready component, stick to the basic technique. If you’re in an experimental mood and browser support allows it, try the newer features - they’re already available in recent versions of Chrome.</p>
61
<p>The complete code lives in an <a>interactive, step-by-step demo</a> - you can go through the implementation at your own pace and play with the parameters along the way.</p>
61
<p>The complete code lives in an <a>interactive, step-by-step demo</a> - you can go through the implementation at your own pace and play with the parameters along the way.</p>
62
<p>Hopefully, building progress bars in 2026 will feel a lot more enjoyable.</p>
62
<p>Hopefully, building progress bars in 2026 will feel a lot more enjoyable.</p>