20 CSS Animated Buttons 13 / 20

CSS Animated Download Button

A download CTA button where clicking triggers a bouncing arrow animation, a progress bar fill, and a completion state — all using CSS transitions and keyframes toggled by JS class changes.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ab-13">
  <p class="ab-13__label">Click the button to download</p>
  <button class="ab-13__btn" id="ab-13-btn">
    <svg class="ab-13__icon" id="ab-13-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
      <polyline points="7 10 12 15 17 10"/>
      <line x1="12" y1="15" x2="12" y2="3"/>
    </svg>
    <span class="ab-13__text" id="ab-13-text">Download v2.4.1</span>
  </button>
  <p class="ab-13__size">macOS · 48.2 MB</p>
</div>
.ab-13,.ab-13 *,.ab-13 *::before,.ab-13 *::after{box-sizing:border-box;margin:0;padding:0}
.ab-13 ::selection{background:#2563eb;color:#fff}
.ab-13{
  font-family:system-ui,sans-serif;
  background:#f0f4ff;
  min-height:100vh;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:1.25rem;
  padding:2rem;
}
.ab-13__label,.ab-13__size{
  font-size:.78rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:#64748b;
}
.ab-13__btn{
  position:relative;
  overflow:hidden;
  display:inline-flex;
  align-items:center;
  gap:.65rem;
  padding:.9rem 2.4rem;
  font-size:1rem;
  font-weight:700;
  color:#fff;
  background:#2563eb;
  border:none;
  border-radius:12px;
  cursor:pointer;
  outline:none;
  transition:background .3s,box-shadow .3s,transform .15s;
  box-shadow:0 4px 16px rgba(37,99,235,.4);
}
.ab-13__btn:hover:not(.is-downloading):not(.is-done){
  filter:brightness(1.08);
  transform:translateY(-2px);
}
.ab-13__btn:active{transform:translateY(0)}
.ab-13__btn::after{
  content:'';
  position:absolute;
  left:0;bottom:0;
  width:100%;height:3px;
  background:rgba(255,255,255,.55);
  transform:scaleX(0);
  transform-origin:left;
  transition:transform 2s linear;
  pointer-events:none;
}
.ab-13__btn.is-downloading{background:#1d4ed8;cursor:not-allowed}
.ab-13__btn.is-downloading::after{transform:scaleX(1)}
.ab-13__btn.is-done{background:#059669;box-shadow:0 4px 16px rgba(5,150,105,.4)}
.ab-13__icon{
  width:20px;height:20px;
  flex-shrink:0;
  transition:transform .2s;
}
.ab-13__btn.is-downloading .ab-13__icon{
  animation:ab-13-bounce .6s ease-in-out infinite;
}
.ab-13__btn.is-done .ab-13__icon{
  animation:none;
  transform:rotate(0);
}
@keyframes ab-13-bounce{
  0%,100%{transform:translateY(0)}
  50%{transform:translateY(4px)}
}
@media(prefers-reduced-motion:reduce){
  .ab-13__btn.is-downloading .ab-13__icon{animation:none}
  .ab-13__btn::after{transition:none}
}
(function(){
  var btn=document.getElementById('ab-13-btn');
  var txt=document.getElementById('ab-13-text');
  var icon=document.getElementById('ab-13-icon');
  if(!btn||!txt)return;
  var CHECK='<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>';
  var DONE_ICON='<polyline points="20 6 9 17 4 12"/>';
  var orig=icon.innerHTML;
  var downloading=false;
  btn.addEventListener('click',function(){
    if(downloading||btn.classList.contains('is-done'))return;
    downloading=true;
    btn.classList.add('is-downloading');
    txt.textContent='Downloading…';
    setTimeout(function(){
      btn.classList.remove('is-downloading');
      btn.classList.add('is-done');
      icon.innerHTML=DONE_ICON;
      txt.textContent='Downloaded!';
      setTimeout(function(){
        btn.classList.remove('is-done');
        icon.innerHTML=orig;
        txt.textContent='Download v2.4.1';
        downloading=false;
      },2000);
    },2200);
  });
})();

How this works

The button wraps a label and an SVG arrow icon. At rest, the arrow sits in a neutral position. On click, JS adds .is-downloading which triggers: (1) a @keyframes ab-13-bounce that translates the arrow icon downward in a repeating bounce, (2) a full-width pseudo-element progress bar that transitions from scaleX(0) to scaleX(1) over 2 s using transform-origin: left, and (3) the label text changes to "Downloading…". After the fill completes, JS swaps the class to .is-done, stopping the bounce and showing a checkmark.

The progress bar lives in the button's ::after pseudo-element with background: rgba(255,255,255,.35) and transform: scaleX(0) at rest. The transition is driven by CSS alone — JS only manages the class names and timing via setTimeout. This keeps all rendering on the GPU's compositor path and avoids JS animation loops.

Customize

  • Change the download duration by updating the setTimeout value in JS from 2000 to 3000 ms and matching the transition-duration on ::after.
  • Swap the progress bar colour by editing the background on .ab-13__btn.is-downloading::after.
  • Replace the SVG arrow with a cloud-download icon by updating the viewBox and path data in the HTML.
  • Add a file size label beneath the button that counts up during the download state using a JS setInterval counter.
  • Use a circular progress ring instead of a linear bar by replacing the ::after fill with an SVG stroke-dashoffset animation on a circle element.

Watch out for

  • The ::after progress bar uses transform: scaleX() which is smooth on the compositor, but the transition only fires when the class changes — ensure the class is added on the next frame after the button renders to avoid a missed transition start.
  • pointer-events: none on the progress pseudo-element is essential — without it the fill layer intercepts clicks mid-download and may inadvertently prevent re-click.
  • On Safari, overflow: hidden on the button clips the progress bar correctly, but the bounce animation on the icon may composite onto a separate layer — verify the icon stays visually within the button bounds.

Browser support

ChromeSafariFirefoxEdge
49+ 10.1+ 44+ 49+

scaleX transitions and SVG animations are universally supported in modern browsers.

Search CodeFronts

Loading…