Back to Components
Animated Download Button with Progress Counter
Component

Animated Download Button with Progress Counter

CodewithLord
February 1, 2026

A premium animated download button built using HTML, CSS, and JavaScript, featuring a progress counter, smooth transitions, 3D motion, and success state animation.


Animated Download Button


This component showcases a high-quality animated download button with a realistic download experience, including:

  • Animated arrow and icon morphing
  • Percentage-based progress counter
  • Smooth 3D button motion
  • Success and restart states
  • Polished micro-interactions

Perfect for modern dashboards, SaaS products, and premium UI designs.


Preview Images


1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <title>Download button animation | CodewithLord</title> 7 <link rel="stylesheet" href="https://public.codepenassets.com/css/reset-2.0.min.css"> 8 <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:400,500,700&amp;display=swap'> 9 <link rel="stylesheet" href="./style.css"> 10</head> 11 12<body> 13 <a class="dl-button" href=""> 14 <div> 15 <div class="icon"> 16 <div> 17 <svg class="arrow" viewBox="0 0 20 18" fill="currentColor"> 18 <polygon points="8 0 12 0 12 9 15 9 10 14 5 9 8 9"></polygon> 19 </svg> 20 <svg class="shape" viewBox="0 0 20 18" fill="currentColor"> 21 <path 22 d="M4.82668561,0 L15.1733144,0 C16.0590479,0 16.8392841,0.582583769 17.0909106,1.43182334 L19.7391982,10.369794 C19.9108349,10.9490677 19.9490212,11.5596963 19.8508905,12.1558403 L19.1646343,16.3248465 C19.0055906,17.2910371 18.1703851,18 17.191192,18 L2.80880804,18 C1.82961488,18 0.994409401,17.2910371 0.835365676,16.3248465 L0.149109507,12.1558403 C0.0509788145,11.5596963 0.0891651114,10.9490677 0.260801785,10.369794 L2.90908938,1.43182334 C3.16071592,0.582583769 3.94095214,0 4.82668561,0 Z"> 23 </path> 24 </svg> 25 </div><span></span> 26 </div> 27 <div class="label"> 28 <div class="show default">&#68;ownload</div> 29 <div class="state"> 30 <div class="counter"> 31 <ul> 32 <li></li> 33 <li>1</li> 34 </ul> 35 <ul> 36 <li>0</li> 37 <li>1</li> 38 <li>2</li> 39 <li>3</li> 40 <li>4</li> 41 <li>5</li> 42 <li>6</li> 43 <li>7</li> 44 <li>8</li> 45 <li>9</li> 46 <li>0</li> 47 </ul> 48 <ul> 49 <li>0</li> 50 <li>1</li> 51 <li>2</li> 52 <li>3</li> 53 <li>4</li> 54 <li>5</li> 55 <li>6</li> 56 <li>7</li> 57 <li>8</li> 58 <li>9</li> 59 <li>0</li> 60 <li>1</li> 61 <li>2</li> 62 <li>3</li> 63 <li>4</li> 64 <li>5</li> 65 <li>6</li> 66 <li>7</li> 67 <li>8</li> 68 <li>9</li> 69 <li>0</li> 70 <li>1</li> 71 <li>2</li> 72 <li>3</li> 73 <li>4</li> 74 <li>5</li> 75 <li>6</li> 76 <li>7</li> 77 <li>8</li> 78 <li>9</li> 79 <li>0</li> 80 </ul><span>%</span> 81 </div><span>Done</span> 82 </div> 83 </div> 84 <div class="progress"></div> 85 </div> 86 </a><a class="restart" href=""> 87 <svg viewBox="0 0 16 16" fill="currentColor"> 88 <path 89 d="M4.5,4.5c1.9-1.9,5.1-1.9,7,0c0.7,0.7,1.2,1.7,1.4,2.7l2-0.3C14.7,5.4,14,4.1,13,3.1c-2.7-2.7-7.1-2.7-9.9,0 L0.9,0.9L0.2,7.3l6.4-0.7L4.5,4.5z"> 90 </path> 91 <path 92 d="M15.8,8.7L9.4,9.4l2.1,2.1c-1.9,1.9-5.1,1.9-7,0c-0.7-0.7-1.2-1.7-1.4-2.7l-2,0.3 C1.3,10.6,2,11.9,3,12.9c1.4,1.4,3.1,2,4.9,2c1.8,0,3.6-0.7,4.9-2l2.2,2.2L15.8,8.7z"> 93 </path> 94 </svg>Restart</a> 95 <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script> 96 <script src="./script.js"></script> 97 98</body> 99 100</html>

CSS Code


1html { 2 box-sizing: border-box; 3 -webkit-font-smoothing: antialiased; 4} 5 6* { 7 box-sizing: inherit; 8} 9 10*:before, 11*:after { 12 box-sizing: inherit; 13} 14 15body { 16 min-height: 100vh; 17 font-family: Roboto, Arial; 18 display: flex; 19 justify-content: center; 20 align-items: center; 21 background: #252432; 22 padding: 20px; 23} 24 25.dl-button { 26 --duration: 4000; 27 --success: #16BF78; 28 --grey-light: #99A3BA; 29 --grey: #6C7486; 30 --grey-dark: #3F4656; 31 --light: #CDD9ED; 32 --shadow: rgba(18, 22, 33, .6); 33 --shadow-dark: rgba(18, 22, 33, .85); 34 display: block; 35 text-decoration: none; 36 perspective: 500px; 37} 38 39.dl-button>div { 40 position: relative; 41 background: #fff; 42 border-radius: 5px; 43 overflow: hidden; 44 display: flex; 45 padding: 16px 24px; 46 box-shadow: 0 4px 12px var(--shadow); 47} 48 49.dl-button>div .icon { 50 --color: var(--grey); 51 margin-right: 12px; 52 position: relative; 53 transform: translateZ(8px); 54} 55 56.dl-button>div .icon div { 57 overflow: hidden; 58 position: relative; 59 width: 20px; 60 height: 22px; 61} 62 63.dl-button>div .icon div:before, 64.dl-button>div .icon div:after { 65 content: ""; 66 position: absolute; 67 width: 2px; 68 height: 2px; 69 top: 2px; 70 transition: opacity 0.3s ease; 71} 72 73.dl-button>div .icon div:before { 74 left: 6px; 75 background-image: radial-gradient(circle at 0 100%, var(--color) 2px, #fff 0px); 76} 77 78.dl-button>div .icon div:after { 79 right: 6px; 80 background-image: radial-gradient(circle at 100% 100%, var(--color) 2px, #fff 0px); 81} 82 83.dl-button>div .icon div svg { 84 width: 20px; 85 height: 18px; 86 display: block; 87 margin-top: 2px; 88 position: relative; 89 z-index: 1; 90} 91 92.dl-button>div .icon div svg.arrow { 93 color: #fff; 94 position: absolute; 95 left: 0; 96 top: 0; 97 z-index: 2; 98 transform: translateY(-1px); 99} 100 101.dl-button>div .icon div svg.shape { 102 color: var(--color); 103 transition: color 0.4s ease; 104} 105 106.dl-button>div .icon span { 107 --s: 1; 108 position: absolute; 109 left: 1px; 110 right: 1px; 111 bottom: 2px; 112 background: var(--color); 113 height: 6px; 114 border-radius: 50%; 115 display: block; 116 transform: translateY(0) scale(var(--s)); 117} 118 119.dl-button>div .label { 120 --color: var(--grey-dark); 121 line-height: 22px; 122 font-size: 16px; 123 font-weight: 500; 124 color: var(--color); 125 position: relative; 126 transition: color 0.4s ease; 127 transform: translateZ(8px); 128} 129 130.dl-button>div .label>div { 131 display: flex; 132 transition: opacity 0.25s ease; 133} 134 135.dl-button>div .label>div:not(.show) { 136 position: absolute; 137 left: 0; 138 top: 0; 139 opacity: 0; 140} 141 142.dl-button>div .label>div.hide { 143 opacity: 0; 144} 145 146.dl-button>div .label>div .counter { 147 overflow: hidden; 148 display: flex; 149 height: 18px; 150 line-height: 18px; 151 margin: 2px 0; 152 position: relative; 153 transition: opacity 0.3s ease; 154} 155 156.dl-button>div .label>div .counter:before, 157.dl-button>div .label>div .counter:after { 158 content: ""; 159 display: block; 160 position: absolute; 161 left: 0; 162 right: 0; 163 height: 3px; 164 z-index: 1; 165} 166 167.dl-button>div .label>div .counter:before { 168 top: 0; 169 background: linear-gradient(to bottom, white 0%, rgba(255, 255, 255, 0) 100%); 170} 171 172.dl-button>div .label>div .counter:after { 173 bottom: 0; 174 background: linear-gradient(to top, white 0%, rgba(255, 255, 255, 0) 100%); 175} 176 177.dl-button>div .label>div .counter span { 178 display: inline-block; 179 margin: 0 4px 0 2px; 180} 181 182.dl-button>div .label>div .counter ul { 183 --y: 0; 184 margin: 0; 185 padding: 0; 186 list-style: none; 187 width: 10px; 188 height: 18px; 189 -webkit-backface-visibility: hidden; 190 transform: translateY(var(--y)) translateZ(0); 191} 192 193.dl-button>div .label>div .counter ul:nth-child(1) { 194 transition: transform calc(var(--duration) * .2ms) ease-in-out; 195} 196 197.dl-button>div .label>div .counter ul:nth-child(2) { 198 transition: transform calc(var(--duration) * .8ms) ease-in-out; 199} 200 201.dl-button>div .label>div .counter ul:nth-child(3) { 202 transition: transform calc(var(--duration) * .8ms) ease-in-out; 203} 204 205.dl-button>div .label>div .counter ul li { 206 width: 10px; 207 height: 18px; 208} 209 210.dl-button>div .label>div .counter.hide { 211 opacity: 0; 212} 213 214.dl-button>div .progress { 215 --s: 0; 216 position: absolute; 217 left: 0; 218 right: 0; 219 bottom: 0; 220 height: 3px; 221 transform-origin: 50% 100%; 222 transform: scaleY(var(--s)); 223 transition: transform 0.4s ease; 224} 225 226.dl-button>div .progress:before, 227.dl-button>div .progress:after { 228 --s: 1; 229 content: ""; 230 background: var(--success); 231 position: absolute; 232 left: 0; 233 top: 0; 234 bottom: 0; 235 right: 0; 236 transform-origin: 0 50%; 237 transform: scaleX(var(--s)); 238} 239 240.dl-button>div .progress:before { 241 opacity: 0.35; 242} 243 244.dl-button>div .progress:after { 245 --s: 0; 246 transition: transform calc(var(--duration) * .9ms) ease-in-out; 247} 248 249.dl-button.active>div { 250 -webkit-animation: button calc(var(--duration) * 1ms) linear forwards; 251 animation: button calc(var(--duration) * 1ms) linear forwards; 252} 253 254.dl-button.active>div .icon div:before, 255.dl-button.active>div .icon div:after { 256 opacity: 0; 257 transition-delay: 0.4s; 258} 259 260.dl-button.active>div .icon svg.arrow { 261 -webkit-animation: arrow calc(var(--duration) * .18ms) linear 4 calc(var(--duration) * .2ms); 262 animation: arrow calc(var(--duration) * .18ms) linear 4 calc(var(--duration) * .2ms); 263} 264 265.dl-button.active>div .icon span { 266 -webkit-animation: span calc(var(--duration) * .18ms) linear 4 calc(var(--duration) * .2ms); 267 animation: span calc(var(--duration) * .18ms) linear 4 calc(var(--duration) * .2ms); 268} 269 270.dl-button.active>div .label>div .counter ul:nth-child(1) { 271 --y: -18px; 272 transition-delay: calc(var(--duration) * .72ms); 273} 274 275.dl-button.active>div .label>div .counter ul:nth-child(2) { 276 --y: -180px; 277 transition-delay: calc(var(--duration) * .09ms); 278 -webkit-animation: motion calc(var(--duration) * .5ms) linear forwards calc(var(--duration) * .19ms); 279 animation: motion calc(var(--duration) * .5ms) linear forwards calc(var(--duration) * .19ms); 280} 281 282.dl-button.active>div .label>div .counter ul:nth-child(3) { 283 --y: -540px; 284 transition-delay: calc(var(--duration) * .075ms); 285 -webkit-animation: motion calc(var(--duration) * .8ms) linear forwards calc(var(--duration) * .075ms); 286 animation: motion calc(var(--duration) * .8ms) linear forwards calc(var(--duration) * .075ms); 287} 288 289.dl-button.active>div .progress { 290 --s: 1; 291 transition-delay: 0.4s; 292} 293 294.dl-button.active>div .progress:after { 295 --s: 1; 296 transition-delay: 0.4s; 297} 298 299.dl-button.done>div .icon { 300 --color: var(--success); 301} 302 303.dl-button.done .label { 304 --color: var(--success); 305} 306 307.dl-button.done .label .counter { 308 width: 0; 309} 310 311@-webkit-keyframes arrow { 312 38% { 313 transform: translateY(100%); 314 opacity: 1; 315 } 316 317 39% { 318 transform: translateY(100%); 319 opacity: 0; 320 } 321 322 40% { 323 transform: translateY(-100%); 324 opacity: 0; 325 } 326 327 41% { 328 transform: translateY(-100%); 329 opacity: 1; 330 } 331 332 100% { 333 transform: translateY(-1px); 334 opacity: 1; 335 } 336} 337 338@keyframes arrow { 339 38% { 340 transform: translateY(100%); 341 opacity: 1; 342 } 343 344 39% { 345 transform: translateY(100%); 346 opacity: 0; 347 } 348 349 40% { 350 transform: translateY(-100%); 351 opacity: 0; 352 } 353 354 41% { 355 transform: translateY(-100%); 356 opacity: 1; 357 } 358 359 100% { 360 transform: translateY(-1px); 361 opacity: 1; 362 } 363} 364 365@-webkit-keyframes span { 366 25% { 367 transform: translateY(2px) scale(var(--s)); 368 } 369 370 55% { 371 transform: translateY(2px) scale(var(--s)); 372 } 373 374 80%, 375 100% { 376 transform: translateY(0) scale(var(--s)); 377 } 378} 379 380@keyframes span { 381 25% { 382 transform: translateY(2px) scale(var(--s)); 383 } 384 385 55% { 386 transform: translateY(2px) scale(var(--s)); 387 } 388 389 80%, 390 100% { 391 transform: translateY(0) scale(var(--s)); 392 } 393} 394 395@-webkit-keyframes motion { 396 397 20%, 398 70% { 399 filter: blur(0.4px); 400 } 401} 402 403@keyframes motion { 404 405 20%, 406 70% { 407 filter: blur(0.4px); 408 } 409} 410 411@-webkit-keyframes button { 412 0% { 413 transform: translateX(0) translateZ(0) scale(1) rotateY(0deg); 414 } 415 416 10% { 417 transform: translateX(0) translateZ(0) scale(0.96) rotateY(0deg); 418 box-shadow: 0 4px 8px var(--shadow-dark); 419 } 420 421 20% { 422 transform: translateX(-16px) translateZ(32px) scale(1) rotateY(-16deg); 423 box-shadow: 4px 12px 20px var(--shadow-dark); 424 } 425 426 85% { 427 transform: translateX(16px) translateZ(32px) scale(1) rotateY(16deg); 428 box-shadow: -4px 12px 20px var(--shadow-dark); 429 } 430 431 95% { 432 transform: translateX(0) translateZ(0) scale(1.12) rotateY(0deg); 433 box-shadow: 0 8px 24px var(--shadow-dark); 434 } 435 436 100% { 437 transform: translateX(0) translateZ(0) scale(1) rotateY(0deg); 438 } 439} 440 441@keyframes button { 442 0% { 443 transform: translateX(0) translateZ(0) scale(1) rotateY(0deg); 444 } 445 446 10% { 447 transform: translateX(0) translateZ(0) scale(0.96) rotateY(0deg); 448 box-shadow: 0 4px 8px var(--shadow-dark); 449 } 450 451 20% { 452 transform: translateX(-16px) translateZ(32px) scale(1) rotateY(-16deg); 453 box-shadow: 4px 12px 20px var(--shadow-dark); 454 } 455 456 85% { 457 transform: translateX(16px) translateZ(32px) scale(1) rotateY(16deg); 458 box-shadow: -4px 12px 20px var(--shadow-dark); 459 } 460 461 95% { 462 transform: translateX(0) translateZ(0) scale(1.12) rotateY(0deg); 463 box-shadow: 0 8px 24px var(--shadow-dark); 464 } 465 466 100% { 467 transform: translateX(0) translateZ(0) scale(1) rotateY(0deg); 468 } 469} 470 471.dl-button.done+.restart { 472 opacity: 1; 473 visibility: visible; 474} 475 476.restart { 477 --grey-dark: #3F4656; 478 position: absolute; 479 bottom: 20%; 480 left: 50%; 481 transform: translateX(-50%); 482 color: var(--grey-dark); 483 font-size: 14px; 484 line-height: 16px; 485 text-decoration: none; 486 opacity: 0; 487 visibility: hidden; 488 transition: opacity 0.4s ease; 489} 490 491.restart svg { 492 width: 16px; 493 height: 16px; 494 margin-right: 4px; 495 display: inline-block; 496 vertical-align: top; 497}

Javascript Code


1$('.dl-button').on('click', e => { 2 3 let btn = $(e.currentTarget), 4 label = btn.find('.label'), 5 counter = label.find('.counter'); 6 7 if (!btn.hasClass('active') && !btn.hasClass('done')) { 8 9 btn.addClass('active'); 10 11 setLabel(label, label.find('.default'), label.find('.state')); 12 13 setTimeout(() => { 14 counter.addClass('hide'); 15 counter.animate({ 16 width: 0 17 }, 400, function () { 18 label.width(label.find('.state > span').width()); 19 counter.removeAttr('style'); 20 }); 21 btn.removeClass('active').addClass('done'); 22 }, getComputedStyle(btn[0]).getPropertyValue('--duration')); 23 24 } 25 26 return false; 27 28}); 29 30$('.restart').on('click', e => { 31 32 let btn = $('.dl-button'), 33 label = btn.find('.label'), 34 counter = label.find('.counter'); 35 36 setLabel(label, label.find('.state'), label.find('.default'), function () { 37 counter.removeClass('hide'); 38 btn.removeClass('done'); 39 }); 40 41 return false; 42 43}); 44 45function setLabel(div, oldD, newD, callback) { 46 oldD.addClass('hide'); 47 div.animate({ 48 width: newD.outerWidth() 49 }, 200, function () { 50 oldD.removeClass('show hide'); 51 newD.addClass('show'); 52 div.removeAttr('style'); 53 if (typeof callback === 'function') { 54 callback(); 55 } 56 }); 57}


Explanation of the Code


HTML Structure

Button built using semantic tag for accessibility.

Nested elements allow independent animation control.

SVG icons provide crisp, scalable visuals.

CSS Animations & Styling

CSS variables manage duration, colors, and shadows.

3D transforms and perspective create depth.

Keyframe animations drive arrow motion and progress flow.

Progress Counter Logic

Animated vertical number lists simulate a rolling counter.

Percentage increments are synced with CSS duration.

Smooth transitions ensure premium user experience.

JavaScript Interaction

Handles state changes: idle → downloading → done.

Prevents multiple clicks during animation.

Restart button resets animation state cleanly.

Use Cases


File downloads

SaaS dashboards

Call-to-action buttons

UI animation showcases

Love this component?

Explore more components and build amazing UIs.

View All Components